@morehao/dsh-skills-console 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -2
- package/client.js +576 -161
- package/lib/core/intro-store.js +156 -0
- package/lib/core/intro.js +430 -0
- package/lib/core/paths.js +2 -0
- package/lib/core/remote.js +103 -1
- package/lib/core/service.js +477 -1
- package/lib/index.js +16 -0
- package/lib/routes.js +50 -2
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -46,12 +46,13 @@ var API = "/api/morehao/skills-console";
|
|
|
46
46
|
function short(sha) {
|
|
47
47
|
return sha ? String(sha).slice(0, 7) : "";
|
|
48
48
|
}
|
|
49
|
-
async function apiCall(method, path, body) {
|
|
49
|
+
async function apiCall(method, path, body, options = {}) {
|
|
50
50
|
const response = await fetch(`${API}${path}`, {
|
|
51
51
|
method,
|
|
52
52
|
headers: { "content-type": "application/json", "x-dsh-skills-console": "1" },
|
|
53
53
|
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
54
|
-
cache: "no-store"
|
|
54
|
+
cache: "no-store",
|
|
55
|
+
...options.signal ? { signal: options.signal } : {}
|
|
55
56
|
});
|
|
56
57
|
let json = {};
|
|
57
58
|
try {
|
|
@@ -302,7 +303,33 @@ var DICT = {
|
|
|
302
303
|
"viewer.info.visibility": "可见性",
|
|
303
304
|
"viewer.info.mode": "状态",
|
|
304
305
|
"viewer.info.status": "更新",
|
|
305
|
-
"viewer.info.none": "无"
|
|
306
|
+
"viewer.info.none": "无",
|
|
307
|
+
"intro.button": "介绍",
|
|
308
|
+
"intro.open": "AI 介绍:这个技能能做什么",
|
|
309
|
+
"intro.title": "AI 介绍",
|
|
310
|
+
"intro.loading": "读取中…",
|
|
311
|
+
"intro.empty": "还没有生成介绍",
|
|
312
|
+
"intro.generate": "生成介绍",
|
|
313
|
+
"intro.regenerate": "重新生成",
|
|
314
|
+
"intro.generating": "生成中…",
|
|
315
|
+
"intro.hint": "会把 SKILL.md 正文发送给当前配置的模型提供方",
|
|
316
|
+
"intro.hintRemote": "会把该技能在 {source}@{commit} 的 SKILL.md 发送给当前配置的模型提供方",
|
|
317
|
+
"intro.meta": "{model} · {time} · AI 生成,可能不准确",
|
|
318
|
+
"intro.stale": "技能内容已更新,介绍可能过期",
|
|
319
|
+
"intro.view": "查看技能",
|
|
320
|
+
"intro.unavailable": "当前宿主没有可用的模型服务",
|
|
321
|
+
"intro.error.noModel": "还没有配置可用的模型",
|
|
322
|
+
"intro.error.rateLimit": "模型限流或额度不足,请稍后再试",
|
|
323
|
+
"intro.error.tooLarge": "技能正文超出模型上下文",
|
|
324
|
+
"intro.error.timeout": "生成超时,请重试",
|
|
325
|
+
"intro.error.auth": "模型提供方认证失败",
|
|
326
|
+
"intro.error.empty": "模型没有返回内容",
|
|
327
|
+
"intro.error.busy": "正在生成,请稍候",
|
|
328
|
+
"intro.error.source": "读不到远端的 SKILL.md(仓库私有、已删除或网络问题)",
|
|
329
|
+
"intro.error.noSource": "订阅快照里没有这个技能,请先点「检查更新」",
|
|
330
|
+
"intro.error.subscription": "订阅不存在,可能已被取消",
|
|
331
|
+
"intro.error.sourceTimeout": "读取远端 SKILL.md 超时,请重试",
|
|
332
|
+
"intro.error.generic": "生成失败,请重试"
|
|
306
333
|
},
|
|
307
334
|
en: {
|
|
308
335
|
"section.title": "Skills Console",
|
|
@@ -535,7 +562,33 @@ var DICT = {
|
|
|
535
562
|
"viewer.info.visibility": "Visibility",
|
|
536
563
|
"viewer.info.mode": "State",
|
|
537
564
|
"viewer.info.status": "Updates",
|
|
538
|
-
"viewer.info.none": "None"
|
|
565
|
+
"viewer.info.none": "None",
|
|
566
|
+
"intro.button": "Intro",
|
|
567
|
+
"intro.open": "AI intro: what this skill does",
|
|
568
|
+
"intro.title": "AI intro",
|
|
569
|
+
"intro.loading": "Loading…",
|
|
570
|
+
"intro.empty": "No intro yet",
|
|
571
|
+
"intro.generate": "Generate intro",
|
|
572
|
+
"intro.regenerate": "Regenerate",
|
|
573
|
+
"intro.generating": "Generating…",
|
|
574
|
+
"intro.hint": "Sends the SKILL.md body to the model provider configured in DSH",
|
|
575
|
+
"intro.hintRemote": "Sends the SKILL.md at {source}@{commit} to the model provider configured in DSH",
|
|
576
|
+
"intro.meta": "{model} · {time} · AI-generated, may be inaccurate",
|
|
577
|
+
"intro.stale": "The skill changed; this intro may be out of date",
|
|
578
|
+
"intro.view": "Open skill",
|
|
579
|
+
"intro.unavailable": "No model service is available in this host",
|
|
580
|
+
"intro.error.noModel": "No usable model is configured",
|
|
581
|
+
"intro.error.rateLimit": "The model is rate-limited or out of quota; try again later",
|
|
582
|
+
"intro.error.tooLarge": "The skill body exceeds the model context window",
|
|
583
|
+
"intro.error.timeout": "Generation timed out; try again",
|
|
584
|
+
"intro.error.auth": "The model provider rejected the credentials",
|
|
585
|
+
"intro.error.empty": "The model returned no content",
|
|
586
|
+
"intro.error.busy": "Already generating; please wait",
|
|
587
|
+
"intro.error.source": "Could not read the remote SKILL.md (private repo, deleted, or network)",
|
|
588
|
+
"intro.error.noSource": "This skill is not in the subscription snapshot; run “Check for updates” first",
|
|
589
|
+
"intro.error.subscription": "The subscription no longer exists",
|
|
590
|
+
"intro.error.sourceTimeout": "Timed out reading the remote SKILL.md; try again",
|
|
591
|
+
"intro.error.generic": "Generation failed; try again"
|
|
539
592
|
}
|
|
540
593
|
};
|
|
541
594
|
function buildTranslate(locale) {
|
|
@@ -546,11 +599,16 @@ function buildTranslate(locale) {
|
|
|
546
599
|
return "zh";
|
|
547
600
|
}
|
|
548
601
|
};
|
|
549
|
-
|
|
602
|
+
const translate = (key) => {
|
|
550
603
|
const id = active();
|
|
551
604
|
const pack = DICT[id] || DICT.zh;
|
|
552
605
|
return pack[key] ?? DICT.zh[key] ?? key;
|
|
553
606
|
};
|
|
607
|
+
Object.defineProperty(translate, "lang", {
|
|
608
|
+
get: () => active() === "en" ? "en" : "zh",
|
|
609
|
+
enumerable: false
|
|
610
|
+
});
|
|
611
|
+
return translate;
|
|
554
612
|
}
|
|
555
613
|
|
|
556
614
|
// client/host.js
|
|
@@ -982,7 +1040,7 @@ function FolderIcon({ size = 12 }) {
|
|
|
982
1040
|
}
|
|
983
1041
|
|
|
984
1042
|
// client/section.js
|
|
985
|
-
var
|
|
1043
|
+
var import_react10 = __toESM(require("react"), 1);
|
|
986
1044
|
|
|
987
1045
|
// client/scroll.js
|
|
988
1046
|
function isScroller(node, tolerance = 4) {
|
|
@@ -1307,6 +1365,11 @@ var CSS = `
|
|
|
1307
1365
|
align-items: center; font-size: 12px; padding: 6px 0; border-top: 1px solid var(--dsw-alias-border-l1); }
|
|
1308
1366
|
.dshsc-entryhead + .dshsc-sub-entry { border-top: 0; }
|
|
1309
1367
|
.dshsc-sub-entry .dshsc-name { font-size: 12px; font-weight: 500; }
|
|
1368
|
+
/* 条目第一列:名字 + ✨ 同一行,作者原描述单独一行(超长省略,完整内容在 title) */
|
|
1369
|
+
.dshsc-sub-skill { min-width: 0; }
|
|
1370
|
+
.dshsc-sub-namerow { display: flex; align-items: center; gap: 6px; min-width: 0; }
|
|
1371
|
+
.dshsc-sub-desc { margin-top: 1px; font-size: 11px; line-height: 15px; color: var(--dsw-alias-label-tertiary);
|
|
1372
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1310
1373
|
.dshsc-state { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--dsw-alias-label-tertiary); }
|
|
1311
1374
|
.dshsc-state.ok svg { color: var(--dsw-alias-state-success-primary); }
|
|
1312
1375
|
.dshsc-sub-entry .dshsc-actions { justify-self: end; display: flex; justify-content: flex-end; }
|
|
@@ -1584,6 +1647,26 @@ var CSS = `
|
|
|
1584
1647
|
.dshsc-info-sec dd { margin: 0; color: var(--dsw-alias-label-primary); overflow-wrap: anywhere; }
|
|
1585
1648
|
.dshsc-info-sec dd.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; }
|
|
1586
1649
|
|
|
1650
|
+
/* 列表行的 ✨ 介绍入口:只占一个小胶囊,不改变行高与列宽 */
|
|
1651
|
+
.dshsc-intro-btn { flex: none; display: inline-flex; align-items: center; gap: 3px;
|
|
1652
|
+
height: 19px; padding: 0 7px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 999px;
|
|
1653
|
+
background: transparent; color: var(--dsw-alias-label-tertiary); font: inherit; font-size: 11px;
|
|
1654
|
+
line-height: 1; cursor: pointer; white-space: nowrap; }
|
|
1655
|
+
.dshsc-intro-btn:hover { color: var(--dsw-alias-label-primary); border-color: var(--dsw-alias-border-l3);
|
|
1656
|
+
background: var(--dsw-alias-bg-layer-1); }
|
|
1657
|
+
.dshsc-intro-btn:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }
|
|
1658
|
+
.dshsc-intro-btn .dshsc-spark { font-size: 10px; color: var(--dsw-alias-brand-primary); }
|
|
1659
|
+
|
|
1660
|
+
/* 介绍浮层:复用 policy 浮层的外壳(fixed + portal),只调内容排版 */
|
|
1661
|
+
.dshsc-intro-pop { width: min(360px, calc(100vw - 16px)); gap: 7px; }
|
|
1662
|
+
.dshsc-intro-text { margin: 0; font-size: 12.5px; line-height: 1.7; color: var(--dsw-alias-label-primary);
|
|
1663
|
+
white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
1664
|
+
.dshsc-intro-note { margin: 0; font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-tertiary); }
|
|
1665
|
+
.dshsc-intro-meta { margin: 0; font-size: 11px; line-height: 15px; color: var(--dsw-alias-label-tertiary); }
|
|
1666
|
+
.dshsc-intro-stale { margin: 0; font-size: 11px; line-height: 15px; color: var(--dsw-alias-state-warn-primary); }
|
|
1667
|
+
.dshsc-intro-error { margin: 0; font-size: 11.5px; line-height: 16px; color: var(--dsw-alias-state-error-primary); }
|
|
1668
|
+
.dshsc-intro-foot { display: flex; align-items: center; justify-content: flex-end; gap: 8px; margin-top: 3px; }
|
|
1669
|
+
|
|
1587
1670
|
/* 正文排版:只用 token,不写死深浅色 */
|
|
1588
1671
|
.dshsc-md { font-size: 13.5px; line-height: 1.72; color: var(--dsw-alias-label-primary); }
|
|
1589
1672
|
.dshsc-md-h { margin: 20px 0 8px; line-height: 1.35; font-weight: 650; letter-spacing: -.008em; }
|
|
@@ -2101,6 +2184,70 @@ function useSkillReader(row) {
|
|
|
2101
2184
|
}, []);
|
|
2102
2185
|
return { state, file, loadFile, retry };
|
|
2103
2186
|
}
|
|
2187
|
+
function useSkillIntro({ name, lang, stamp, enabled = true, endpoint = null }) {
|
|
2188
|
+
const base = endpoint || `/skills/${encodeURIComponent(name)}/intro`;
|
|
2189
|
+
const [state, setState] = import_react2.default.useState({ loading: true, intro: null, capability: null, error: null });
|
|
2190
|
+
const [generating, setGenerating] = import_react2.default.useState(false);
|
|
2191
|
+
const [genError, setGenError] = import_react2.default.useState(null);
|
|
2192
|
+
const seq = import_react2.default.useRef(0);
|
|
2193
|
+
const controller = import_react2.default.useRef(null);
|
|
2194
|
+
import_react2.default.useEffect(() => {
|
|
2195
|
+
if (!name || !enabled) {
|
|
2196
|
+
setState({ loading: false, intro: null, capability: null, error: null });
|
|
2197
|
+
return;
|
|
2198
|
+
}
|
|
2199
|
+
const ticket = seq.current + 1;
|
|
2200
|
+
seq.current = ticket;
|
|
2201
|
+
setState({ loading: true, intro: null, capability: null, error: null });
|
|
2202
|
+
void (async () => {
|
|
2203
|
+
try {
|
|
2204
|
+
const result = await apiCall("GET", `${base}?lang=${encodeURIComponent(lang || "zh")}`);
|
|
2205
|
+
if (ticket !== seq.current) return;
|
|
2206
|
+
setState({ loading: false, intro: result.intro || null, capability: result.capability || null, error: null });
|
|
2207
|
+
} catch (error) {
|
|
2208
|
+
if (ticket !== seq.current) return;
|
|
2209
|
+
setState({
|
|
2210
|
+
loading: false,
|
|
2211
|
+
intro: null,
|
|
2212
|
+
capability: null,
|
|
2213
|
+
error: { code: error?.code || null, message: error?.message || "读取失败" }
|
|
2214
|
+
});
|
|
2215
|
+
}
|
|
2216
|
+
})();
|
|
2217
|
+
}, [name, lang, stamp, enabled, base]);
|
|
2218
|
+
import_react2.default.useEffect(() => () => {
|
|
2219
|
+
if (controller.current) {
|
|
2220
|
+
controller.current.abort();
|
|
2221
|
+
controller.current = null;
|
|
2222
|
+
}
|
|
2223
|
+
}, [name, lang, base]);
|
|
2224
|
+
const generate = import_react2.default.useCallback(async (force = false) => {
|
|
2225
|
+
if (!name) return null;
|
|
2226
|
+
if (controller.current) return null;
|
|
2227
|
+
const ctrl = new AbortController();
|
|
2228
|
+
controller.current = ctrl;
|
|
2229
|
+
setGenerating(true);
|
|
2230
|
+
setGenError(null);
|
|
2231
|
+
try {
|
|
2232
|
+
const result = await apiCall(
|
|
2233
|
+
"POST",
|
|
2234
|
+
base,
|
|
2235
|
+
{ lang: lang || "zh", force: Boolean(force) },
|
|
2236
|
+
{ signal: ctrl.signal }
|
|
2237
|
+
);
|
|
2238
|
+
setState({ loading: false, intro: result.intro || null, capability: result.capability || null, error: null });
|
|
2239
|
+
return result;
|
|
2240
|
+
} catch (error) {
|
|
2241
|
+
if (error?.name === "AbortError") return null;
|
|
2242
|
+
setGenError({ code: error?.code || null, message: error?.message || "生成失败" });
|
|
2243
|
+
return null;
|
|
2244
|
+
} finally {
|
|
2245
|
+
if (controller.current === ctrl) controller.current = null;
|
|
2246
|
+
setGenerating(false);
|
|
2247
|
+
}
|
|
2248
|
+
}, [name, lang, base]);
|
|
2249
|
+
return { ...state, generating, genError, generate };
|
|
2250
|
+
}
|
|
2104
2251
|
|
|
2105
2252
|
// client/views/header.js
|
|
2106
2253
|
function installSpec(plugin) {
|
|
@@ -2294,18 +2441,164 @@ function Toolbar({
|
|
|
2294
2441
|
}
|
|
2295
2442
|
|
|
2296
2443
|
// client/views/installed.js
|
|
2297
|
-
var
|
|
2444
|
+
var import_react5 = __toESM(require("react"), 1);
|
|
2298
2445
|
|
|
2299
|
-
// client/views/
|
|
2446
|
+
// client/views/intro-popover.js
|
|
2300
2447
|
var import_react3 = __toESM(require("react"), 1);
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2448
|
+
var ERROR_KEY = {
|
|
2449
|
+
"error.skills.intro-no-model": "intro.error.noModel",
|
|
2450
|
+
"error.skills.intro-rate-limit": "intro.error.rateLimit",
|
|
2451
|
+
"error.skills.intro-quota": "intro.error.rateLimit",
|
|
2452
|
+
"error.skills.intro-too-large": "intro.error.tooLarge",
|
|
2453
|
+
"error.skills.intro-timeout": "intro.error.timeout",
|
|
2454
|
+
"error.skills.intro-auth": "intro.error.auth",
|
|
2455
|
+
"error.skills.intro-empty": "intro.error.empty",
|
|
2456
|
+
"error.skills.intro-busy": "intro.error.busy",
|
|
2457
|
+
"error.skills.intro-source": "intro.error.source",
|
|
2458
|
+
"error.skills.subscription-not-found": "intro.error.subscription",
|
|
2459
|
+
"error.skills.intro-source-timeout": "intro.error.sourceTimeout",
|
|
2460
|
+
"error.skills.intro-no-source": "intro.error.noSource"
|
|
2461
|
+
};
|
|
2462
|
+
function formatWhen(iso) {
|
|
2463
|
+
if (!iso) return null;
|
|
2464
|
+
const date = new Date(iso);
|
|
2465
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
2466
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
2467
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
2468
|
+
}
|
|
2469
|
+
function modelLabel(intro) {
|
|
2470
|
+
if (!intro?.model) return "—";
|
|
2471
|
+
return intro.provider ? `${intro.provider}/${intro.model}` : intro.model;
|
|
2472
|
+
}
|
|
2473
|
+
function IntroPopover({
|
|
2474
|
+
t,
|
|
2475
|
+
name,
|
|
2476
|
+
stamp = "",
|
|
2477
|
+
subtitle = null,
|
|
2478
|
+
endpoint = null,
|
|
2479
|
+
capability,
|
|
2480
|
+
anchor,
|
|
2481
|
+
anchorEl,
|
|
2482
|
+
hint = null,
|
|
2483
|
+
viewLabel = null,
|
|
2484
|
+
onClose,
|
|
2485
|
+
onView = null
|
|
2486
|
+
}) {
|
|
2305
2487
|
const ref = import_react3.default.useRef(null);
|
|
2306
2488
|
const [pos, setPos] = import_react3.default.useState({ left: 0, top: 0, visibility: "hidden" });
|
|
2489
|
+
const enabled = capability?.enabled !== false;
|
|
2490
|
+
const available = capability?.available !== false;
|
|
2491
|
+
const intro = useSkillIntro({
|
|
2492
|
+
name,
|
|
2493
|
+
lang: t.lang || "zh",
|
|
2494
|
+
stamp,
|
|
2495
|
+
enabled,
|
|
2496
|
+
endpoint
|
|
2497
|
+
});
|
|
2307
2498
|
useOverlayLayer(true, onClose);
|
|
2308
2499
|
import_react3.default.useLayoutEffect(() => {
|
|
2500
|
+
const node = ref.current;
|
|
2501
|
+
if (!node || !anchor || typeof window === "undefined") return;
|
|
2502
|
+
const rect = node.getBoundingClientRect();
|
|
2503
|
+
const width = rect.width || 360;
|
|
2504
|
+
const height = rect.height || 220;
|
|
2505
|
+
let left = anchor.left;
|
|
2506
|
+
left = Math.max(8, Math.min(left, window.innerWidth - width - 8));
|
|
2507
|
+
let top = anchor.bottom + 8;
|
|
2508
|
+
if (top + height > window.innerHeight - 8) {
|
|
2509
|
+
top = Math.max(8, anchor.top - height - 8);
|
|
2510
|
+
}
|
|
2511
|
+
setPos({ left, top, visibility: "visible" });
|
|
2512
|
+
}, [anchor, intro.loading, intro.intro?.text, intro.generating, intro.genError]);
|
|
2513
|
+
import_react3.default.useEffect(() => {
|
|
2514
|
+
if (typeof document === "undefined") return void 0;
|
|
2515
|
+
const onPointerDown = (event) => {
|
|
2516
|
+
const target = event?.target;
|
|
2517
|
+
if (ref.current?.contains?.(target)) return;
|
|
2518
|
+
if (anchorEl?.contains?.(target)) return;
|
|
2519
|
+
onClose();
|
|
2520
|
+
};
|
|
2521
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
2522
|
+
return () => document.removeEventListener("pointerdown", onPointerDown, true);
|
|
2523
|
+
}, [anchorEl, onClose]);
|
|
2524
|
+
const failure = intro.genError || intro.error;
|
|
2525
|
+
const failureKey = failure?.code ? ERROR_KEY[failure.code] || "intro.error.generic" : null;
|
|
2526
|
+
const meta = intro.intro ? t("intro.meta").replace("{model}", modelLabel(intro.intro)).replace("{time}", formatWhen(intro.intro.generatedAt) || "—") : "";
|
|
2527
|
+
const actions = [];
|
|
2528
|
+
if (intro.intro) {
|
|
2529
|
+
actions.push(h("button", {
|
|
2530
|
+
type: "button",
|
|
2531
|
+
key: "regen",
|
|
2532
|
+
className: "dshsc-btn ghost",
|
|
2533
|
+
disabled: intro.generating || !available,
|
|
2534
|
+
onClick: () => {
|
|
2535
|
+
void intro.generate(true);
|
|
2536
|
+
}
|
|
2537
|
+
}, intro.generating ? t("intro.generating") : t("intro.regenerate")));
|
|
2538
|
+
if (onView) {
|
|
2539
|
+
actions.push(h("button", {
|
|
2540
|
+
type: "button",
|
|
2541
|
+
key: "view",
|
|
2542
|
+
className: "dshsc-btn primary",
|
|
2543
|
+
onClick: onView
|
|
2544
|
+
}, viewLabel || t("intro.view")));
|
|
2545
|
+
}
|
|
2546
|
+
} else {
|
|
2547
|
+
actions.push(h("button", {
|
|
2548
|
+
type: "button",
|
|
2549
|
+
key: "gen",
|
|
2550
|
+
className: "dshsc-btn primary",
|
|
2551
|
+
disabled: intro.generating || !available || !enabled,
|
|
2552
|
+
onClick: () => {
|
|
2553
|
+
void intro.generate(false);
|
|
2554
|
+
}
|
|
2555
|
+
}, intro.generating ? t("intro.generating") : t("intro.generate")));
|
|
2556
|
+
}
|
|
2557
|
+
return portalize(h(
|
|
2558
|
+
"div",
|
|
2559
|
+
{
|
|
2560
|
+
ref,
|
|
2561
|
+
className: "dshsc-policy-pop dshsc-intro-pop",
|
|
2562
|
+
role: "dialog",
|
|
2563
|
+
"aria-label": `${t("intro.title")} · ${name}`,
|
|
2564
|
+
style: { left: `${pos.left}px`, top: `${pos.top}px`, visibility: pos.visibility }
|
|
2565
|
+
},
|
|
2566
|
+
h(
|
|
2567
|
+
"div",
|
|
2568
|
+
{ className: "dshsc-policy-head" },
|
|
2569
|
+
h(
|
|
2570
|
+
"div",
|
|
2571
|
+
null,
|
|
2572
|
+
h("div", { className: "dshsc-policy-title" }, `✨ ${t("intro.title")}`),
|
|
2573
|
+
h("div", { className: "dshsc-policy-sub", title: subtitle || name }, subtitle || name)
|
|
2574
|
+
),
|
|
2575
|
+
h("button", {
|
|
2576
|
+
type: "button",
|
|
2577
|
+
className: "dshsc-policy-close",
|
|
2578
|
+
"aria-label": t("action.close"),
|
|
2579
|
+
onClick: onClose
|
|
2580
|
+
}, "×")
|
|
2581
|
+
),
|
|
2582
|
+
intro.loading ? h("p", { className: "dshsc-intro-note" }, t("intro.loading")) : intro.intro ? h("p", { className: "dshsc-intro-text" }, intro.intro.text) : h("p", { className: "dshsc-intro-note" }, t("intro.empty")),
|
|
2583
|
+
intro.intro?.stale ? h("p", { className: "dshsc-intro-stale" }, t("intro.stale")) : null,
|
|
2584
|
+
failureKey ? h("p", { className: "dshsc-intro-error" }, t(failureKey)) : null,
|
|
2585
|
+
meta ? h("p", { className: "dshsc-intro-meta" }, meta) : null,
|
|
2586
|
+
available && !intro.intro && !intro.loading ? h("p", { className: "dshsc-intro-meta" }, hint || t("intro.hint")) : null,
|
|
2587
|
+
!available ? h("p", { className: "dshsc-intro-error" }, t("intro.unavailable")) : null,
|
|
2588
|
+
h("div", { className: "dshsc-intro-foot" }, actions)
|
|
2589
|
+
));
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
// client/views/policy-popover.js
|
|
2593
|
+
var import_react4 = __toESM(require("react"), 1);
|
|
2594
|
+
function PolicyPopover({ t, row, anchor, anchorEl, busy, onClose, onSave, onUnpark }) {
|
|
2595
|
+
const initial = row.mode === "hidden" ? "auto" : row.mode;
|
|
2596
|
+
const [mode, setMode] = import_react4.default.useState(initial);
|
|
2597
|
+
const [confirming, setConfirming] = import_react4.default.useState(false);
|
|
2598
|
+
const ref = import_react4.default.useRef(null);
|
|
2599
|
+
const [pos, setPos] = import_react4.default.useState({ left: 0, top: 0, visibility: "hidden" });
|
|
2600
|
+
useOverlayLayer(true, onClose);
|
|
2601
|
+
import_react4.default.useLayoutEffect(() => {
|
|
2309
2602
|
const node = ref.current;
|
|
2310
2603
|
if (!node || !anchor || typeof window === "undefined") return;
|
|
2311
2604
|
const rect = node.getBoundingClientRect();
|
|
@@ -2319,7 +2612,7 @@ function PolicyPopover({ t, row, anchor, anchorEl, busy, onClose, onSave, onUnpa
|
|
|
2319
2612
|
}
|
|
2320
2613
|
setPos({ left, top, visibility: "visible" });
|
|
2321
2614
|
}, [anchor, confirming, mode]);
|
|
2322
|
-
|
|
2615
|
+
import_react4.default.useEffect(() => {
|
|
2323
2616
|
if (typeof document === "undefined") return void 0;
|
|
2324
2617
|
const onPointerDown = (event) => {
|
|
2325
2618
|
const target = event?.target;
|
|
@@ -2473,13 +2766,28 @@ function InstalledTab({
|
|
|
2473
2766
|
actions
|
|
2474
2767
|
}) {
|
|
2475
2768
|
const updatable = (row) => UPDATABLE.has(row.status);
|
|
2476
|
-
const [policy, setPolicy] =
|
|
2769
|
+
const [policy, setPolicy] = import_react5.default.useState(null);
|
|
2770
|
+
const [intro, setIntro] = import_react5.default.useState(null);
|
|
2477
2771
|
const sharedTargets = new Set((data?.agents?.detected || []).filter((agent) => agent.kind === "agent" && agent.sharedHome === "yes").map((agent) => agent.target));
|
|
2478
|
-
|
|
2772
|
+
import_react5.default.useEffect(() => {
|
|
2479
2773
|
if (!policy) return;
|
|
2480
2774
|
if (rows.some((row) => row.name === policy.row.name)) return;
|
|
2481
2775
|
setPolicy(null);
|
|
2482
2776
|
}, [policy, rows]);
|
|
2777
|
+
import_react5.default.useEffect(() => {
|
|
2778
|
+
if (!intro) return;
|
|
2779
|
+
if (rows.some((row) => row.name === intro.row.name)) return;
|
|
2780
|
+
setIntro(null);
|
|
2781
|
+
}, [intro, rows]);
|
|
2782
|
+
function openIntro(event, row) {
|
|
2783
|
+
const el = event.currentTarget;
|
|
2784
|
+
const rect = el.getBoundingClientRect();
|
|
2785
|
+
setIntro({
|
|
2786
|
+
row,
|
|
2787
|
+
anchorEl: el,
|
|
2788
|
+
anchor: { left: rect.left, top: rect.top, bottom: rect.bottom, width: rect.width, height: rect.height }
|
|
2789
|
+
});
|
|
2790
|
+
}
|
|
2483
2791
|
function openPolicy(event, row) {
|
|
2484
2792
|
if (busy !== null || row.kind === "file") return;
|
|
2485
2793
|
if (row.mode === "manual" && row.origin === "author") return;
|
|
@@ -2673,6 +2981,18 @@ function InstalledTab({
|
|
|
2673
2981
|
"aria-haspopup": "dialog",
|
|
2674
2982
|
onClick: () => actions.onView(row)
|
|
2675
2983
|
}, row.name),
|
|
2984
|
+
data?.intro ? h(
|
|
2985
|
+
"button",
|
|
2986
|
+
{
|
|
2987
|
+
type: "button",
|
|
2988
|
+
className: "dshsc-intro-btn",
|
|
2989
|
+
title: t("intro.open"),
|
|
2990
|
+
"aria-haspopup": "dialog",
|
|
2991
|
+
onClick: (event) => openIntro(event, row)
|
|
2992
|
+
},
|
|
2993
|
+
h("span", { className: "dshsc-spark", "aria-hidden": "true" }, "✨"),
|
|
2994
|
+
t("intro.button")
|
|
2995
|
+
) : null,
|
|
2676
2996
|
statusTag(row)
|
|
2677
2997
|
),
|
|
2678
2998
|
sourceLine(row),
|
|
@@ -2786,6 +3106,7 @@ function InstalledTab({
|
|
|
2786
3106
|
}, dot, h("span", null, label), h("span", { className: "n" }, counts[id]));
|
|
2787
3107
|
}
|
|
2788
3108
|
function listBar() {
|
|
3109
|
+
const updateDot = counts.update > 0 ? h("i", { className: "dshsc-fdot" }) : null;
|
|
2789
3110
|
return h(
|
|
2790
3111
|
"div",
|
|
2791
3112
|
{ className: "dshsc-listbar" },
|
|
@@ -2793,7 +3114,7 @@ function InstalledTab({
|
|
|
2793
3114
|
"div",
|
|
2794
3115
|
{ className: "dshsc-filters" },
|
|
2795
3116
|
filterChip("all", t("filter.all")),
|
|
2796
|
-
filterChip("update", t("filter.update"),
|
|
3117
|
+
filterChip("update", t("filter.update"), updateDot),
|
|
2797
3118
|
filterChip("unknown", t("filter.unknown")),
|
|
2798
3119
|
filterChip("manual", t("filter.manual")),
|
|
2799
3120
|
filterChip("off", t("filter.off"))
|
|
@@ -2921,6 +3242,20 @@ function InstalledTab({
|
|
|
2921
3242
|
)
|
|
2922
3243
|
);
|
|
2923
3244
|
}
|
|
3245
|
+
const introPopover = intro && data?.intro ? h(IntroPopover, {
|
|
3246
|
+
t,
|
|
3247
|
+
name: intro.row.name,
|
|
3248
|
+
stamp: [intro.row.commit || "", intro.row.currentHash || "", intro.row.updatedAt || "", intro.row.status || ""].join("|"),
|
|
3249
|
+
anchor: intro.anchor,
|
|
3250
|
+
anchorEl: intro.anchorEl,
|
|
3251
|
+
capability: data.intro,
|
|
3252
|
+
onClose: () => setIntro(null),
|
|
3253
|
+
onView: () => {
|
|
3254
|
+
const target = intro.row;
|
|
3255
|
+
setIntro(null);
|
|
3256
|
+
actions.onView(target);
|
|
3257
|
+
}
|
|
3258
|
+
}) : null;
|
|
2924
3259
|
const popover = policy ? h(PolicyPopover, {
|
|
2925
3260
|
t,
|
|
2926
3261
|
row: policy.row,
|
|
@@ -2939,7 +3274,7 @@ function InstalledTab({
|
|
|
2939
3274
|
actions.onRequestUnpark(target);
|
|
2940
3275
|
}
|
|
2941
3276
|
}) : null;
|
|
2942
|
-
return h(
|
|
3277
|
+
return h(import_react5.default.Fragment, null, content(), popover, introPopover);
|
|
2943
3278
|
}
|
|
2944
3279
|
|
|
2945
3280
|
// client/views/agent-tip.js
|
|
@@ -2974,10 +3309,14 @@ function AgentTip({ t, agentTip, agentTipPos, nodeRef }) {
|
|
|
2974
3309
|
}
|
|
2975
3310
|
|
|
2976
3311
|
// client/views/subscriptions.js
|
|
3312
|
+
var import_react6 = __toESM(require("react"), 1);
|
|
2977
3313
|
function repoOf(source) {
|
|
2978
3314
|
const value = String(source || "").trim().toLowerCase().replace(/\.git$/, "");
|
|
2979
3315
|
return value || null;
|
|
2980
3316
|
}
|
|
3317
|
+
function shortCommit(value) {
|
|
3318
|
+
return typeof value === "string" ? value.slice(0, 7) : "";
|
|
3319
|
+
}
|
|
2981
3320
|
function SubscriptionsTab({
|
|
2982
3321
|
t,
|
|
2983
3322
|
subscriptions,
|
|
@@ -2985,12 +3324,72 @@ function SubscriptionsTab({
|
|
|
2985
3324
|
expanded,
|
|
2986
3325
|
busy,
|
|
2987
3326
|
writable,
|
|
3327
|
+
capability = null,
|
|
2988
3328
|
onToggle,
|
|
2989
3329
|
onRefresh,
|
|
2990
3330
|
onDelete,
|
|
2991
3331
|
onInstall,
|
|
2992
3332
|
onAdd
|
|
2993
3333
|
}) {
|
|
3334
|
+
const [intro, setIntro] = import_react6.default.useState(null);
|
|
3335
|
+
import_react6.default.useEffect(() => {
|
|
3336
|
+
if (!intro) return;
|
|
3337
|
+
const subscription = subscriptions.find((item) => item.id === intro.subscriptionId);
|
|
3338
|
+
const entries = subscription?.snapshot?.entries || [];
|
|
3339
|
+
if (entries.some((entry) => entry.name === intro.name)) return;
|
|
3340
|
+
setIntro(null);
|
|
3341
|
+
}, [intro, subscriptions]);
|
|
3342
|
+
function openIntro(event, subscription, entry) {
|
|
3343
|
+
const el = event.currentTarget;
|
|
3344
|
+
const rect = el.getBoundingClientRect();
|
|
3345
|
+
setIntro({
|
|
3346
|
+
subscriptionId: subscription.id,
|
|
3347
|
+
name: entry.name,
|
|
3348
|
+
anchorEl: el,
|
|
3349
|
+
anchor: { left: rect.left, top: rect.top, bottom: rect.bottom, width: rect.width, height: rect.height }
|
|
3350
|
+
});
|
|
3351
|
+
}
|
|
3352
|
+
function introButton(subscription, entry) {
|
|
3353
|
+
return h(
|
|
3354
|
+
"button",
|
|
3355
|
+
{
|
|
3356
|
+
type: "button",
|
|
3357
|
+
className: "dshsc-intro-btn",
|
|
3358
|
+
title: t("intro.open"),
|
|
3359
|
+
"aria-haspopup": "dialog",
|
|
3360
|
+
onClick: (clickEvent) => openIntro(clickEvent, subscription, entry)
|
|
3361
|
+
},
|
|
3362
|
+
h("span", { className: "dshsc-spark", "aria-hidden": "true" }, "✨"),
|
|
3363
|
+
t("intro.button")
|
|
3364
|
+
);
|
|
3365
|
+
}
|
|
3366
|
+
const introSubscription = intro ? subscriptions.find((item) => item.id === intro.subscriptionId) || null : null;
|
|
3367
|
+
const introEntry = introSubscription ? (introSubscription.snapshot?.entries || []).find((entry) => entry.name === intro.name) || null : null;
|
|
3368
|
+
const introPopover = intro && introEntry && capability ? (() => {
|
|
3369
|
+
const commit = introSubscription.snapshot?.commit || "";
|
|
3370
|
+
const installedAlready = rows.some((row) => {
|
|
3371
|
+
if (row.name !== introEntry.name) return false;
|
|
3372
|
+
if (row.subscriptionId === introSubscription.id) return true;
|
|
3373
|
+
return repoOf(row.source) === repoOf(introSubscription.source);
|
|
3374
|
+
});
|
|
3375
|
+
return h(IntroPopover, {
|
|
3376
|
+
t,
|
|
3377
|
+
name: introEntry.name,
|
|
3378
|
+
stamp: commit,
|
|
3379
|
+
subtitle: `${introSubscription.source}@${shortCommit(commit)}`,
|
|
3380
|
+
endpoint: `/subscriptions/${encodeURIComponent(introSubscription.id)}/entries/${encodeURIComponent(introEntry.name)}/intro`,
|
|
3381
|
+
capability,
|
|
3382
|
+
anchor: intro.anchor,
|
|
3383
|
+
anchorEl: intro.anchorEl,
|
|
3384
|
+
hint: t("intro.hintRemote").replace("{source}", introSubscription.source).replace("{commit}", shortCommit(commit) || "—"),
|
|
3385
|
+
viewLabel: t("action.install"),
|
|
3386
|
+
onView: installedAlready ? null : () => {
|
|
3387
|
+
setIntro(null);
|
|
3388
|
+
void onInstall(introSubscription, introEntry);
|
|
3389
|
+
},
|
|
3390
|
+
onClose: () => setIntro(null)
|
|
3391
|
+
});
|
|
3392
|
+
})() : null;
|
|
2994
3393
|
if (subscriptions.length === 0) {
|
|
2995
3394
|
return h(
|
|
2996
3395
|
"div",
|
|
@@ -3013,118 +3412,133 @@ function SubscriptionsTab({
|
|
|
3013
3412
|
);
|
|
3014
3413
|
}
|
|
3015
3414
|
return h(
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
// 表头与仓库行共用同一个滚动容器,位置 sticky;宽屏 4 列,窄屏收成「仓库」/「操作」。
|
|
3415
|
+
import_react6.default.Fragment,
|
|
3416
|
+
null,
|
|
3019
3417
|
h(
|
|
3020
3418
|
"div",
|
|
3021
|
-
{ className: "dshsc-
|
|
3419
|
+
{ className: "dshsc-tabpane" },
|
|
3420
|
+
// 表头与仓库行共用同一个滚动容器,位置 sticky;宽屏 4 列,窄屏收成「仓库」/「操作」。
|
|
3022
3421
|
h(
|
|
3023
3422
|
"div",
|
|
3024
|
-
{ className: "dshsc-
|
|
3025
|
-
h(
|
|
3026
|
-
h("div", { className: "dshsc-href" }, t("column.ref")),
|
|
3027
|
-
h("div", { className: "dshsc-hcount" }, t("column.entries")),
|
|
3028
|
-
h("div", { className: "dshsc-hact" }, t("column.actions"))
|
|
3029
|
-
),
|
|
3030
|
-
subscriptions.map((subscription) => {
|
|
3031
|
-
const entries = subscription.snapshot?.entries || [];
|
|
3032
|
-
const repo = repoOf(subscription.source);
|
|
3033
|
-
const rowsByName = new Map(rows.map((row) => [row.name, row]));
|
|
3034
|
-
const installedRow = (name) => {
|
|
3035
|
-
const row = rowsByName.get(name);
|
|
3036
|
-
if (!row) return null;
|
|
3037
|
-
if (row.subscriptionId === subscription.id) return row;
|
|
3038
|
-
return repo && repoOf(row.source) === repo ? row : null;
|
|
3039
|
-
};
|
|
3040
|
-
const installedCount = entries.filter((entry) => installedRow(entry.name)).length;
|
|
3041
|
-
const open = Boolean(expanded[subscription.id]);
|
|
3042
|
-
return h(
|
|
3423
|
+
{ className: "dshsc-listscroll dshsc-subscroll" },
|
|
3424
|
+
h(
|
|
3043
3425
|
"div",
|
|
3044
|
-
{ className: "dshsc-
|
|
3045
|
-
h(
|
|
3426
|
+
{ className: "dshsc-subhead" },
|
|
3427
|
+
h("div", { className: "dshsc-hrepo" }, t("column.repo")),
|
|
3428
|
+
h("div", { className: "dshsc-href" }, t("column.ref")),
|
|
3429
|
+
h("div", { className: "dshsc-hcount" }, t("column.entries")),
|
|
3430
|
+
h("div", { className: "dshsc-hact" }, t("column.actions"))
|
|
3431
|
+
),
|
|
3432
|
+
subscriptions.map((subscription) => {
|
|
3433
|
+
const entries = subscription.snapshot?.entries || [];
|
|
3434
|
+
const repo = repoOf(subscription.source);
|
|
3435
|
+
const rowsByName = new Map(rows.map((row) => [row.name, row]));
|
|
3436
|
+
const installedRow = (name) => {
|
|
3437
|
+
const row = rowsByName.get(name);
|
|
3438
|
+
if (!row) return null;
|
|
3439
|
+
if (row.subscriptionId === subscription.id) return row;
|
|
3440
|
+
return repo && repoOf(row.source) === repo ? row : null;
|
|
3441
|
+
};
|
|
3442
|
+
const installedCount = entries.filter((entry) => installedRow(entry.name)).length;
|
|
3443
|
+
const open = Boolean(expanded[subscription.id]);
|
|
3444
|
+
return h(
|
|
3046
3445
|
"div",
|
|
3047
|
-
{ className: "dshsc-sub-
|
|
3446
|
+
{ className: "dshsc-sub-block", key: subscription.id },
|
|
3048
3447
|
h(
|
|
3049
3448
|
"div",
|
|
3050
|
-
{ className: "dshsc-sub-
|
|
3051
|
-
h(
|
|
3052
|
-
|
|
3053
|
-
"
|
|
3054
|
-
"
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
h("span", { className: "dshsc-tag dshsc-subref" }, subscription.ref || t("subscription.defaultRef")),
|
|
3449
|
+
{ className: "dshsc-sub-row" },
|
|
3450
|
+
h(
|
|
3451
|
+
"div",
|
|
3452
|
+
{ className: "dshsc-sub-repo" },
|
|
3453
|
+
h("button", {
|
|
3454
|
+
className: "dshsc-caret",
|
|
3455
|
+
"aria-expanded": String(open),
|
|
3456
|
+
"aria-label": `${subscription.source} ${open ? t("action.collapse") : t("action.expand")}`,
|
|
3457
|
+
onClick: () => onToggle(subscription.id)
|
|
3458
|
+
}, h(CaretIcon, { open })),
|
|
3459
|
+
h("span", { className: "dshsc-name" }, subscription.source)
|
|
3460
|
+
),
|
|
3461
|
+
// 宽屏铺成 Ref / 条目两列;窄屏回到仓库名下方一行。
|
|
3064
3462
|
h(
|
|
3065
|
-
"
|
|
3066
|
-
{ className: "dshsc-
|
|
3067
|
-
|
|
3463
|
+
"div",
|
|
3464
|
+
{ className: "dshsc-sub-meta" },
|
|
3465
|
+
h("span", { className: "dshsc-tag dshsc-subref" }, subscription.ref || t("subscription.defaultRef")),
|
|
3466
|
+
h(
|
|
3467
|
+
"span",
|
|
3468
|
+
{ className: "dshsc-substat" },
|
|
3469
|
+
`${entries.length} ${t("skillCount")} · ${t("installed")} ${installedCount}`
|
|
3470
|
+
)
|
|
3471
|
+
),
|
|
3472
|
+
h(
|
|
3473
|
+
"div",
|
|
3474
|
+
{ className: "dshsc-actions" },
|
|
3475
|
+
h("button", {
|
|
3476
|
+
className: "dshsc-btn sq",
|
|
3477
|
+
disabled: busy !== null,
|
|
3478
|
+
title: t("action.checkUpdates"),
|
|
3479
|
+
"aria-label": `${t("action.checkUpdates")} ${subscription.source}`,
|
|
3480
|
+
onClick: () => void onRefresh(subscription)
|
|
3481
|
+
}, h(RefreshIcon, { size: 13 })),
|
|
3482
|
+
h("button", { className: "dshsc-btn ghost danger", disabled: busy !== null, onClick: () => void onDelete(subscription) }, t("action.unsubscribe"))
|
|
3068
3483
|
)
|
|
3069
3484
|
),
|
|
3070
|
-
h(
|
|
3071
|
-
"div",
|
|
3072
|
-
{ className: "dshsc-actions" },
|
|
3073
|
-
h("button", {
|
|
3074
|
-
className: "dshsc-btn sq",
|
|
3075
|
-
disabled: busy !== null,
|
|
3076
|
-
title: t("action.checkUpdates"),
|
|
3077
|
-
"aria-label": `${t("action.checkUpdates")} ${subscription.source}`,
|
|
3078
|
-
onClick: () => void onRefresh(subscription)
|
|
3079
|
-
}, h(RefreshIcon, { size: 13 })),
|
|
3080
|
-
h("button", { className: "dshsc-btn ghost danger", disabled: busy !== null, onClick: () => void onDelete(subscription) }, t("action.unsubscribe"))
|
|
3081
|
-
)
|
|
3082
|
-
),
|
|
3083
|
-
open ? h(
|
|
3084
|
-
"div",
|
|
3085
|
-
{ className: "dshsc-sub-list" },
|
|
3086
|
-
h(
|
|
3485
|
+
open ? h(
|
|
3087
3486
|
"div",
|
|
3088
|
-
{ className: "dshsc-
|
|
3089
|
-
h(
|
|
3090
|
-
h("div", null, t("column.status")),
|
|
3091
|
-
h("div", { className: "right" }, t("column.actions"))
|
|
3092
|
-
),
|
|
3093
|
-
entries.map((entry) => {
|
|
3094
|
-
const installedAlready = Boolean(installedRow(entry.name));
|
|
3095
|
-
return h(
|
|
3487
|
+
{ className: "dshsc-sub-list" },
|
|
3488
|
+
h(
|
|
3096
3489
|
"div",
|
|
3097
|
-
{ className: "dshsc-
|
|
3098
|
-
h("
|
|
3099
|
-
h(
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
h(
|
|
3490
|
+
{ className: "dshsc-entryhead" },
|
|
3491
|
+
h("div", null, t("column.skill")),
|
|
3492
|
+
h("div", null, t("column.status")),
|
|
3493
|
+
h("div", { className: "right" }, t("column.actions"))
|
|
3494
|
+
),
|
|
3495
|
+
entries.map((entry) => {
|
|
3496
|
+
const installedAlready = Boolean(installedRow(entry.name));
|
|
3497
|
+
return h(
|
|
3106
3498
|
"div",
|
|
3107
|
-
{ className: "dshsc-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3499
|
+
{ className: "dshsc-sub-entry", key: entry.name },
|
|
3500
|
+
h(
|
|
3501
|
+
"div",
|
|
3502
|
+
{ className: "dshsc-sub-skill" },
|
|
3503
|
+
h(
|
|
3504
|
+
"div",
|
|
3505
|
+
{ className: "dshsc-sub-namerow" },
|
|
3506
|
+
h("span", { className: "dshsc-name" }, entry.name),
|
|
3507
|
+
capability ? introButton(subscription, entry) : null
|
|
3508
|
+
),
|
|
3509
|
+
entry.description ? h("div", { className: "dshsc-sub-desc", title: entry.description }, entry.description) : null
|
|
3510
|
+
),
|
|
3511
|
+
h(
|
|
3512
|
+
"span",
|
|
3513
|
+
{ className: `dshsc-state${installedAlready ? " ok" : ""}` },
|
|
3514
|
+
installedAlready ? "✓ " : "",
|
|
3515
|
+
installedAlready ? t("installed") : t("notInstalled")
|
|
3516
|
+
),
|
|
3517
|
+
h(
|
|
3518
|
+
"div",
|
|
3519
|
+
{ className: "dshsc-actions" },
|
|
3520
|
+
installedAlready ? h("span", { className: "dshsc-noop" }, "-") : h("button", {
|
|
3521
|
+
className: "dshsc-btn",
|
|
3522
|
+
disabled: !writable || busy !== null,
|
|
3523
|
+
onClick: () => void onInstall(subscription, entry)
|
|
3524
|
+
}, t("action.install"))
|
|
3525
|
+
)
|
|
3526
|
+
);
|
|
3527
|
+
})
|
|
3528
|
+
) : null
|
|
3529
|
+
);
|
|
3530
|
+
})
|
|
3531
|
+
)
|
|
3532
|
+
),
|
|
3533
|
+
introPopover
|
|
3120
3534
|
);
|
|
3121
3535
|
}
|
|
3122
3536
|
|
|
3123
3537
|
// client/views/modals.js
|
|
3124
|
-
var
|
|
3538
|
+
var import_react8 = __toESM(require("react"), 1);
|
|
3125
3539
|
|
|
3126
3540
|
// client/views/add-skill.js
|
|
3127
|
-
var
|
|
3541
|
+
var import_react7 = __toESM(require("react"), 1);
|
|
3128
3542
|
|
|
3129
3543
|
// client/views/overlay.js
|
|
3130
3544
|
function Overlay({ t, title, children, footer, onCancel }) {
|
|
@@ -3154,13 +3568,13 @@ function Overlay({ t, title, children, footer, onCancel }) {
|
|
|
3154
3568
|
|
|
3155
3569
|
// client/views/add-skill.js
|
|
3156
3570
|
function AddSkillModal({ t, onCancel, onProbe, onInstall }) {
|
|
3157
|
-
const [source, setSource] =
|
|
3158
|
-
const [phase, setPhase] =
|
|
3159
|
-
const [options, setOptions] =
|
|
3160
|
-
const [selected, setSelected] =
|
|
3161
|
-
const [manual, setManual] =
|
|
3162
|
-
const [skipped, setSkipped] =
|
|
3163
|
-
const [error, setError] =
|
|
3571
|
+
const [source, setSource] = import_react7.default.useState("");
|
|
3572
|
+
const [phase, setPhase] = import_react7.default.useState("idle");
|
|
3573
|
+
const [options, setOptions] = import_react7.default.useState([]);
|
|
3574
|
+
const [selected, setSelected] = import_react7.default.useState(() => /* @__PURE__ */ new Set());
|
|
3575
|
+
const [manual, setManual] = import_react7.default.useState("");
|
|
3576
|
+
const [skipped, setSkipped] = import_react7.default.useState(0);
|
|
3577
|
+
const [error, setError] = import_react7.default.useState(null);
|
|
3164
3578
|
const reset = (value) => {
|
|
3165
3579
|
setSource(value);
|
|
3166
3580
|
setPhase("idle");
|
|
@@ -3296,7 +3710,7 @@ function AddSkillModal({ t, onCancel, onProbe, onInstall }) {
|
|
|
3296
3710
|
|
|
3297
3711
|
// client/views/modals.js
|
|
3298
3712
|
function FormModal({ t, title, hint, fields, onCancel, onSubmit }) {
|
|
3299
|
-
const [form, setForm] =
|
|
3713
|
+
const [form, setForm] = import_react8.default.useState(() => Object.fromEntries(fields.map((field) => [field.key, ""])));
|
|
3300
3714
|
const incomplete = fields.some(
|
|
3301
3715
|
(field) => field.required !== false && !String(form[field.key] || "").trim()
|
|
3302
3716
|
);
|
|
@@ -3344,7 +3758,7 @@ function ExportsModal({ t, row, agents, onCancel, onSave }) {
|
|
|
3344
3758
|
const candidates = agents.filter(
|
|
3345
3759
|
(agent) => agent.kind === "agent" && agent.exists && agent.exportable
|
|
3346
3760
|
);
|
|
3347
|
-
const [targets, setTargets] =
|
|
3761
|
+
const [targets, setTargets] = import_react8.default.useState(() => {
|
|
3348
3762
|
const available = new Set(candidates.map((agent) => agent.target));
|
|
3349
3763
|
return new Set((row.exports || []).map((item) => item.target).filter((target) => available.has(target)));
|
|
3350
3764
|
});
|
|
@@ -3386,7 +3800,7 @@ function ExportsModal({ t, row, agents, onCancel, onSave }) {
|
|
|
3386
3800
|
);
|
|
3387
3801
|
}
|
|
3388
3802
|
function DefaultExportModal({ t, settings, agents, onCancel, onSave }) {
|
|
3389
|
-
const [targets, setTargets] =
|
|
3803
|
+
const [targets, setTargets] = import_react8.default.useState(() => new Set(
|
|
3390
3804
|
Array.isArray(settings.exportTargets) ? settings.exportTargets : []
|
|
3391
3805
|
));
|
|
3392
3806
|
const toggle = (target) => setTargets((prev) => {
|
|
@@ -3515,7 +3929,7 @@ ${conflicts.map((item) => `${item.label}: ${item.linkPath}`).join("\n")}`,
|
|
|
3515
3929
|
}
|
|
3516
3930
|
|
|
3517
3931
|
// client/views/skill-reader.js
|
|
3518
|
-
var
|
|
3932
|
+
var import_react9 = __toESM(require("react"), 1);
|
|
3519
3933
|
|
|
3520
3934
|
// client/markdown.js
|
|
3521
3935
|
function classifyHref(href) {
|
|
@@ -3951,7 +4365,7 @@ function formatBytes(bytes) {
|
|
|
3951
4365
|
if (value < 1024 * 1024) return `${(value / 1024).toFixed(value < 10 * 1024 ? 1 : 0)} KB`;
|
|
3952
4366
|
return `${(value / (1024 * 1024)).toFixed(1)} MB`;
|
|
3953
4367
|
}
|
|
3954
|
-
function
|
|
4368
|
+
function formatWhen2(iso) {
|
|
3955
4369
|
if (!iso) return null;
|
|
3956
4370
|
const date = new Date(iso);
|
|
3957
4371
|
if (Number.isNaN(date.getTime())) return null;
|
|
@@ -4011,37 +4425,37 @@ function SkillReader({
|
|
|
4011
4425
|
onToast
|
|
4012
4426
|
}) {
|
|
4013
4427
|
const { state, file, loadFile, retry } = useSkillReader(row);
|
|
4014
|
-
const [tab, setTab] = import_react7.default.useState(initialTab);
|
|
4015
|
-
const [format, setFormat] = import_react7.default.useState("rendered");
|
|
4016
|
-
const [docOverride, setDocOverride] = import_react7.default.useState(null);
|
|
4017
|
-
const [fileFormat, setFileFormat] = import_react7.default.useState("rendered");
|
|
4018
|
-
const [tocActive, setTocActive] = import_react7.default.useState(null);
|
|
4019
|
-
const [descOpen, setDescOpen] = import_react7.default.useState(false);
|
|
4020
|
-
const [descClamped, setDescClamped] = import_react7.default.useState(false);
|
|
4021
|
-
const bodyRef = import_react7.default.useRef(null);
|
|
4022
|
-
const dialogRef = import_react7.default.useRef(null);
|
|
4023
|
-
const descRef = import_react7.default.useRef(null);
|
|
4024
|
-
const scrollRef = import_react7.default.useRef(0);
|
|
4025
|
-
useOverlayLayer(variant === "drawer" || variant === "page", onClose);
|
|
4026
4428
|
const name = row?.name || "";
|
|
4429
|
+
const [tab, setTab] = import_react9.default.useState(initialTab);
|
|
4430
|
+
const [format, setFormat] = import_react9.default.useState("rendered");
|
|
4431
|
+
const [docOverride, setDocOverride] = import_react9.default.useState(null);
|
|
4432
|
+
const [fileFormat, setFileFormat] = import_react9.default.useState("rendered");
|
|
4433
|
+
const [tocActive, setTocActive] = import_react9.default.useState(null);
|
|
4434
|
+
const [descOpen, setDescOpen] = import_react9.default.useState(false);
|
|
4435
|
+
const [descClamped, setDescClamped] = import_react9.default.useState(false);
|
|
4436
|
+
const bodyRef = import_react9.default.useRef(null);
|
|
4437
|
+
const dialogRef = import_react9.default.useRef(null);
|
|
4438
|
+
const descRef = import_react9.default.useRef(null);
|
|
4439
|
+
const scrollRef = import_react9.default.useRef(0);
|
|
4440
|
+
useOverlayLayer(variant === "drawer" || variant === "page", onClose);
|
|
4027
4441
|
const tree = state.tree;
|
|
4028
4442
|
const files = tree?.files || [];
|
|
4029
4443
|
const fileCount = files.filter((entry) => entry.kind === "file").length;
|
|
4030
4444
|
const bodyText = docOverride ? docOverride.content : state.body?.content || "";
|
|
4031
4445
|
const description = row?.description || state.body?.meta?.description || "";
|
|
4032
4446
|
const headings = tab === "doc" && bodyText ? parseCached(bodyText).headings : [];
|
|
4033
|
-
|
|
4447
|
+
import_react9.default.useEffect(() => {
|
|
4034
4448
|
setDocOverride(null);
|
|
4035
4449
|
scrollRef.current = 0;
|
|
4036
4450
|
}, [name]);
|
|
4037
|
-
const descOwnerRef =
|
|
4038
|
-
|
|
4451
|
+
const descOwnerRef = import_react9.default.useRef(name);
|
|
4452
|
+
import_react9.default.useEffect(() => {
|
|
4039
4453
|
if (descOwnerRef.current === name) return;
|
|
4040
4454
|
descOwnerRef.current = name;
|
|
4041
4455
|
setDescOpen(false);
|
|
4042
4456
|
setDescClamped(false);
|
|
4043
4457
|
}, [name]);
|
|
4044
|
-
|
|
4458
|
+
import_react9.default.useLayoutEffect(() => {
|
|
4045
4459
|
const node = descRef.current;
|
|
4046
4460
|
if (!node) return void 0;
|
|
4047
4461
|
const measure = () => {
|
|
@@ -4063,16 +4477,16 @@ function SkillReader({
|
|
|
4063
4477
|
window.removeEventListener("resize", measure);
|
|
4064
4478
|
};
|
|
4065
4479
|
}, [description, descOpen]);
|
|
4066
|
-
|
|
4480
|
+
import_react9.default.useEffect(() => {
|
|
4067
4481
|
if (!initialFile) return;
|
|
4068
4482
|
setTab("files");
|
|
4069
4483
|
void loadFile(initialFile);
|
|
4070
4484
|
}, [initialFile, loadFile]);
|
|
4071
|
-
|
|
4485
|
+
import_react9.default.useEffect(() => {
|
|
4072
4486
|
const node = bodyRef.current;
|
|
4073
4487
|
if (node && scrollRef.current) node.scrollTop = scrollRef.current;
|
|
4074
4488
|
});
|
|
4075
|
-
|
|
4489
|
+
import_react9.default.useEffect(() => {
|
|
4076
4490
|
if (variant !== "drawer" || typeof document === "undefined") return void 0;
|
|
4077
4491
|
const previous = document.activeElement;
|
|
4078
4492
|
const node = dialogRef.current;
|
|
@@ -4099,12 +4513,12 @@ function SkillReader({
|
|
|
4099
4513
|
previous?.focus?.();
|
|
4100
4514
|
};
|
|
4101
4515
|
}, [variant, onClose]);
|
|
4102
|
-
|
|
4516
|
+
import_react9.default.useEffect(() => {
|
|
4103
4517
|
if (variant !== "page" || typeof document === "undefined") return;
|
|
4104
4518
|
const node = document.getElementById("dshsc-reader-name");
|
|
4105
4519
|
node?.focus?.();
|
|
4106
4520
|
}, [variant, name]);
|
|
4107
|
-
|
|
4521
|
+
import_react9.default.useEffect(() => {
|
|
4108
4522
|
if (variant !== "page" || typeof window === "undefined") return void 0;
|
|
4109
4523
|
const node = bodyRef.current;
|
|
4110
4524
|
if (!node?.querySelectorAll) return void 0;
|
|
@@ -4200,7 +4614,7 @@ function SkillReader({
|
|
|
4200
4614
|
h("span", { className: "dshsc-code" }, row?.source ? `${row.source}${row.commit ? `#${short(row.commit)}` : row.ref ? `#${row.ref}` : ""}` : t("meta.noUpstream"))
|
|
4201
4615
|
),
|
|
4202
4616
|
tree ? h("span", null, t("viewer.fileCount").replace("{n}", String(fileCount))) : null,
|
|
4203
|
-
row?.updatedAt ? h("span", null,
|
|
4617
|
+
row?.updatedAt ? h("span", null, formatWhen2(row.updatedAt)) : null
|
|
4204
4618
|
)
|
|
4205
4619
|
);
|
|
4206
4620
|
const tabsBar = h(
|
|
@@ -4417,8 +4831,8 @@ function SkillReader({
|
|
|
4417
4831
|
["viewer.info.hash", hash ? h("span", { className: "mono" }, hash) : t("viewer.info.none"), "mono"]
|
|
4418
4832
|
];
|
|
4419
4833
|
const localRows = [
|
|
4420
|
-
["viewer.info.installedAt",
|
|
4421
|
-
["viewer.info.updatedAt",
|
|
4834
|
+
["viewer.info.installedAt", formatWhen2(row?.installedAt) || t("viewer.info.none")],
|
|
4835
|
+
["viewer.info.updatedAt", formatWhen2(row?.updatedAt) || t("viewer.info.none")],
|
|
4422
4836
|
["viewer.info.size", tree ? `${formatBytes(tree.totalBytes)} · ${t("viewer.fileCount").replace("{n}", String(fileCount))}` : t("viewer.info.none")],
|
|
4423
4837
|
["viewer.info.localChange", row?.status === "locally-modified" || row?.status === "conflict" ? t("viewer.info.localChangeYes") : t("viewer.info.localChangeNone")]
|
|
4424
4838
|
];
|
|
@@ -4542,19 +4956,19 @@ function SkillReader({
|
|
|
4542
4956
|
var UPDATABLE2 = /* @__PURE__ */ new Set(["update-available", "locally-modified", "conflict"]);
|
|
4543
4957
|
function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
4544
4958
|
const { data, error, busy, toast, setToast, setBusy, reload, run } = useSkillsData();
|
|
4545
|
-
const [tab, setTab] =
|
|
4546
|
-
const [view, setView] =
|
|
4547
|
-
const [modal, setModal] =
|
|
4548
|
-
const [expanded, setExpanded] =
|
|
4549
|
-
const [placementBusy, setPlacementBusy] =
|
|
4550
|
-
const [placementOverride, setPlacementOverride] =
|
|
4551
|
-
const [query, setQuery] =
|
|
4552
|
-
const [viewer, setViewer] =
|
|
4553
|
-
const rootRef =
|
|
4554
|
-
const scrollPos =
|
|
4555
|
-
const restoreScroll =
|
|
4556
|
-
const [filter, setFilter] =
|
|
4557
|
-
const [builtinOpen, setBuiltinOpen] =
|
|
4959
|
+
const [tab, setTab] = import_react10.default.useState("installed");
|
|
4960
|
+
const [view, setView] = import_react10.default.useState("flat");
|
|
4961
|
+
const [modal, setModal] = import_react10.default.useState(null);
|
|
4962
|
+
const [expanded, setExpanded] = import_react10.default.useState({});
|
|
4963
|
+
const [placementBusy, setPlacementBusy] = import_react10.default.useState(null);
|
|
4964
|
+
const [placementOverride, setPlacementOverride] = import_react10.default.useState(null);
|
|
4965
|
+
const [query, setQuery] = import_react10.default.useState("");
|
|
4966
|
+
const [viewer, setViewer] = import_react10.default.useState(null);
|
|
4967
|
+
const rootRef = import_react10.default.useRef(null);
|
|
4968
|
+
const scrollPos = import_react10.default.useRef(null);
|
|
4969
|
+
const restoreScroll = import_react10.default.useRef(null);
|
|
4970
|
+
const [filter, setFilter] = import_react10.default.useState("all");
|
|
4971
|
+
const [builtinOpen, setBuiltinOpen] = import_react10.default.useState(false);
|
|
4558
4972
|
const { settingsOpen, settingsRef, settingsPlacement, closeSettings, toggleSettings } = useSettingsPopover();
|
|
4559
4973
|
const { agentTip, agentTipPos, agentTipRef, openAgentTip, closeAgentTip } = useAgentTip();
|
|
4560
4974
|
const rows = data?.installs || [];
|
|
@@ -4577,7 +4991,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
4577
4991
|
const agentLabels = row.exports.map((item) => item.label);
|
|
4578
4992
|
return [row.name, row.source || "", ...agentLabels].join(" ").toLowerCase().includes(queryText);
|
|
4579
4993
|
});
|
|
4580
|
-
|
|
4994
|
+
import_react10.default.useEffect(() => {
|
|
4581
4995
|
if (!viewer || !data) return;
|
|
4582
4996
|
if (rows.some((row) => row.name === viewer.name)) return;
|
|
4583
4997
|
setViewer(null);
|
|
@@ -4611,7 +5025,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
4611
5025
|
if (viewer?.mode === "page") restoreScroll.current = scrollPos.current;
|
|
4612
5026
|
setViewer(null);
|
|
4613
5027
|
}
|
|
4614
|
-
|
|
5028
|
+
import_react10.default.useLayoutEffect(() => {
|
|
4615
5029
|
if (restoreScroll.current === null) return;
|
|
4616
5030
|
const target = restoreScroll.current;
|
|
4617
5031
|
restoreScroll.current = null;
|
|
@@ -4680,7 +5094,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
4680
5094
|
onToast: (next) => setToast(next)
|
|
4681
5095
|
}) : null;
|
|
4682
5096
|
const listNode = h(
|
|
4683
|
-
|
|
5097
|
+
import_react10.default.Fragment,
|
|
4684
5098
|
null,
|
|
4685
5099
|
h(Header, {
|
|
4686
5100
|
t,
|
|
@@ -4738,6 +5152,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
4738
5152
|
expanded,
|
|
4739
5153
|
busy,
|
|
4740
5154
|
writable,
|
|
5155
|
+
capability: data?.intro || null,
|
|
4741
5156
|
...subscriptionActions
|
|
4742
5157
|
}),
|
|
4743
5158
|
busy ? h("div", { className: "dshsc-meta" }, `${t("busy.working")} ${busy}`) : null
|