@morehao/dsh-skills-console 0.5.0 → 0.5.2
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 +27 -3
- package/client.js +688 -300
- 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) {
|
|
@@ -1076,6 +1134,19 @@ var CSS = `
|
|
|
1076
1134
|
.dshsc-pop-label em { font-style: normal; font-size: 11px; color: var(--dsw-alias-label-tertiary); }
|
|
1077
1135
|
.dshsc-pop-name { display: inline-flex; align-items: baseline; gap: 6px; }
|
|
1078
1136
|
.dshsc-pop-hint { font-size: 11px; line-height: 1.5; color: var(--dsw-alias-label-tertiary); }
|
|
1137
|
+
/* 与 DSH 自带开关同尺寸/同 token:关是 border-l3 轨道,开是 brand-primary 轨道。
|
|
1138
|
+
0.5.0 换三档 chip 时这组规则被一起删掉,但「侧边栏入口」还在用 .dshsc-switch,补回来。 */
|
|
1139
|
+
.dshsc-switch { box-sizing: border-box; position: relative; flex: none; width: 36px; height: 20px; padding: 2px;
|
|
1140
|
+
border: 0; border-radius: 999px; background: var(--dsw-alias-border-l3); cursor: pointer;
|
|
1141
|
+
transition: background .14s ease; }
|
|
1142
|
+
.dshsc-switch:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }
|
|
1143
|
+
.dshsc-switch:disabled { cursor: not-allowed; opacity: .5; }
|
|
1144
|
+
.dshsc-switch[aria-checked="true"] { background: var(--dsw-alias-brand-primary); }
|
|
1145
|
+
.dshsc-knob { display: block; width: 16px; height: 16px; border-radius: 50%;
|
|
1146
|
+
background: var(--dsw-alias-switch-thumb, #fff); box-shadow: 0 1px 2px rgba(0, 0, 0, .28);
|
|
1147
|
+
transition: transform .12s ease, background .12s ease; }
|
|
1148
|
+
.dshsc-switch[aria-checked="true"] .dshsc-knob { transform: translateX(16px);
|
|
1149
|
+
background: var(--dsw-alias-label-primary-foreground, #fff); }
|
|
1079
1150
|
|
|
1080
1151
|
.dshsc-btn { font: inherit; font-size: 12px; line-height: 1; padding: 7px 11px; border-radius: 8px; cursor: pointer;
|
|
1081
1152
|
white-space: nowrap;
|
|
@@ -1299,20 +1370,31 @@ var CSS = `
|
|
|
1299
1370
|
.dshsc-sub-list { display: flex; flex-direction: column; padding: 0 12px 7px 46px;
|
|
1300
1371
|
background: color-mix(in srgb, var(--dsw-alias-bg-layer-2) 22%, transparent);
|
|
1301
1372
|
border-top: 1px solid var(--dsw-alias-border-l1); }
|
|
1373
|
+
/* 条目操作列 92px 是照两枚按钮预留的,眼下每行只有一枚「安装」(两字 ≈ 43px:0 9px padding + 边框)。
|
|
1374
|
+
表头「操作」和已装行的破折号都按按钮宽度做一个右对齐的居中盒子:三者落在同一条中轴上,
|
|
1375
|
+
既不贴着列右缘,也不用改列宽 / 挪按钮。换语言或字号时盒子只是不再严格等于按钮宽度。 */
|
|
1376
|
+
.dshsc-sub-list { --dshsc-entry-act: 44px; }
|
|
1302
1377
|
.dshsc-entryhead { display: grid; grid-template-columns: minmax(0, 1fr) 110px 92px; gap: 10px;
|
|
1303
1378
|
align-items: center; height: 26px; border-bottom: 1px solid var(--dsw-alias-border-l1);
|
|
1304
1379
|
color: var(--dsw-alias-label-tertiary); font-size: 10px; font-weight: 500; letter-spacing: .02em; }
|
|
1305
|
-
.dshsc-entryhead .right {
|
|
1380
|
+
.dshsc-entryhead .right { justify-self: end; box-sizing: border-box; width: var(--dshsc-entry-act);
|
|
1381
|
+
text-align: center; padding-right: 0; }
|
|
1306
1382
|
.dshsc-sub-entry { display: grid; grid-template-columns: minmax(0, 1fr) 110px 92px; gap: 10px;
|
|
1307
1383
|
align-items: center; font-size: 12px; padding: 6px 0; border-top: 1px solid var(--dsw-alias-border-l1); }
|
|
1308
1384
|
.dshsc-entryhead + .dshsc-sub-entry { border-top: 0; }
|
|
1309
1385
|
.dshsc-sub-entry .dshsc-name { font-size: 12px; font-weight: 500; }
|
|
1386
|
+
/* 条目第一列:名字 + ✨ 同一行,作者原描述单独一行(超长省略,完整内容在 title) */
|
|
1387
|
+
.dshsc-sub-skill { min-width: 0; }
|
|
1388
|
+
.dshsc-sub-namerow { display: flex; align-items: center; gap: 6px; min-width: 0; }
|
|
1389
|
+
.dshsc-sub-desc { margin-top: 1px; font-size: 11px; line-height: 15px; color: var(--dsw-alias-label-tertiary);
|
|
1390
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1310
1391
|
.dshsc-state { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--dsw-alias-label-tertiary); }
|
|
1311
1392
|
.dshsc-state.ok svg { color: var(--dsw-alias-state-success-primary); }
|
|
1312
1393
|
.dshsc-sub-entry .dshsc-actions { justify-self: end; display: flex; justify-content: flex-end; }
|
|
1313
1394
|
.dshsc-sub-entry .dshsc-btn { height: 24px; padding: 0 9px; font-size: 11.5px; }
|
|
1314
|
-
/*
|
|
1315
|
-
.dshsc-sub-entry .dshsc-noop {
|
|
1395
|
+
/* 已装条目的操作列没有动作:破折号占位,居中在按钮宽度里,和表头、安装按钮同一条中轴 */
|
|
1396
|
+
.dshsc-sub-entry .dshsc-noop { box-sizing: border-box; width: var(--dshsc-entry-act); padding: 0;
|
|
1397
|
+
text-align: center; color: var(--dsw-alias-label-tertiary); font-size: 11.5px; line-height: 24px; }
|
|
1316
1398
|
/* 设置页的真实宽度常常远小于窗口(侧栏 + 页面缩放),视口 media query 不可靠;
|
|
1317
1399
|
以 .dshsc-root 自身宽度为准,窄面板时切两列,避免固定列把 Agent 标签挤成竖排。
|
|
1318
1400
|
断点取 860 而不是贴着面板常见的 ~900:四列网格需要 280 + 150 + 操作列 + 3×14 gap + 中间列,
|
|
@@ -1341,7 +1423,7 @@ var CSS = `
|
|
|
1341
1423
|
}
|
|
1342
1424
|
.dshsc-code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; }
|
|
1343
1425
|
|
|
1344
|
-
/* 调用策略 chip
|
|
1426
|
+
/* 调用策略 chip:一个入口显示当前模式;点开三档弹窗。窄列里允许省略,不撑破 102px */
|
|
1345
1427
|
.dshsc-chip { box-sizing: border-box; display: inline-flex; align-items: center; gap: 6px; max-width: 100%;
|
|
1346
1428
|
height: 24px; padding: 0 9px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 999px;
|
|
1347
1429
|
background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-secondary); font-family: inherit;
|
|
@@ -1365,40 +1447,40 @@ var CSS = `
|
|
|
1365
1447
|
.dshsc-chip.legacy { color: var(--dsw-alias-label-tertiary); background: var(--dsw-alias-bg-layer-2); }
|
|
1366
1448
|
.dshsc-chip.muted { color: var(--dsw-alias-label-tertiary); cursor: default; opacity: .8; }
|
|
1367
1449
|
|
|
1368
|
-
|
|
1369
|
-
|
|
1450
|
+
/* 锚定浮层外壳:介绍浮层等共用(fixed + portal 到宿主弹窗),不再和调用策略弹窗共用 */
|
|
1451
|
+
.dshsc-floatpop { position: fixed; z-index: 70; box-sizing: border-box; width: min(380px, calc(100vw - 16px));
|
|
1452
|
+
max-height: min(600px, calc(100vh - 16px)); overflow: hidden; padding: 14px;
|
|
1370
1453
|
background: var(--dsw-alias-bg-base); border: 1px solid var(--dsw-alias-border-l1); border-radius: 14px;
|
|
1371
1454
|
box-shadow: 0 18px 48px rgba(0,0,0,.32), 0 2px 8px rgba(0,0,0,.16);
|
|
1372
1455
|
display: flex; flex-direction: column; gap: 10px; }
|
|
1373
|
-
.dshsc-
|
|
1374
|
-
.dshsc-
|
|
1375
|
-
.dshsc-
|
|
1376
|
-
.dshsc-
|
|
1456
|
+
.dshsc-floatpop-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; flex: none; }
|
|
1457
|
+
.dshsc-floatpop-title { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary); }
|
|
1458
|
+
.dshsc-floatpop-sub { margin: 2px 0 0; font-size: 11px; line-height: 16px; color: var(--dsw-alias-label-tertiary); }
|
|
1459
|
+
.dshsc-floatpop-close { flex: none; width: 24px; height: 24px; margin: -4px -4px 0 0; border: 0; border-radius: 6px;
|
|
1377
1460
|
background: transparent; color: var(--dsw-alias-label-secondary); font-size: 18px; line-height: 1; cursor: pointer; }
|
|
1378
|
-
.dshsc-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1461
|
+
.dshsc-floatpop-close:hover { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); }
|
|
1462
|
+
|
|
1463
|
+
/* 调用策略:标准居中弹窗(Overlay)里的单选列表;行距 / 圆点尺寸对齐共享设置的 .dshsc-check */
|
|
1464
|
+
.dshsc-policy-options { max-height: none; overflow: visible; }
|
|
1465
|
+
.dshsc-policyopt { box-sizing: border-box; display: flex; align-items: flex-start; gap: 9px; width: 100%;
|
|
1466
|
+
padding: 5px 8px; text-align: left; border: 0; border-radius: 8px;
|
|
1467
|
+
background: transparent; color: var(--dsw-alias-label-primary); font-family: inherit; cursor: pointer; }
|
|
1468
|
+
.dshsc-policyopt:hover:not(:disabled) { background: var(--dsw-alias-bg-layer-2); }
|
|
1469
|
+
.dshsc-policyopt:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: -2px; }
|
|
1386
1470
|
.dshsc-policyopt:disabled { cursor: not-allowed; opacity: .55; }
|
|
1387
1471
|
.dshsc-policyopt.static { cursor: default; }
|
|
1388
|
-
.dshsc-policyopt
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1472
|
+
.dshsc-policyopt.static:hover { background: transparent; }
|
|
1473
|
+
.dshsc-policyopt-dot { box-sizing: border-box; flex: none; width: 15px; height: 15px; margin-top: 2px;
|
|
1474
|
+
border-radius: 50%; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-1); }
|
|
1475
|
+
.dshsc-policyopt.active .dshsc-policyopt-dot { border: 5px solid var(--dsw-alias-brand-primary); }
|
|
1392
1476
|
.dshsc-policyopt-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
1393
|
-
.dshsc-policyopt-body b { font-size:
|
|
1477
|
+
.dshsc-policyopt-body b { font-size: 13px; font-weight: 600; }
|
|
1394
1478
|
.dshsc-policyopt-body small { font-size: 11px; line-height: 15px; color: var(--dsw-alias-label-tertiary); }
|
|
1395
1479
|
.dshsc-policy-note { padding: 7px 9px; border-radius: 8px; background: var(--dsw-alias-bg-layer-2);
|
|
1396
1480
|
color: var(--dsw-alias-label-secondary); font-size: 11px; line-height: 15px; }
|
|
1397
1481
|
.dshsc-policy-note.warn, .dshsc-policy-note.author { color: var(--dsw-alias-state-warn-primary);
|
|
1398
1482
|
background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 9%, transparent); }
|
|
1399
1483
|
.dshsc-policy-hidden { display: flex; flex-direction: column; gap: 8px; }
|
|
1400
|
-
.dshsc-policy-restore { align-self: flex-start; }
|
|
1401
|
-
.dshsc-policy-foot { display: flex; justify-content: flex-end; gap: 6px; }
|
|
1402
1484
|
.dshsc-policy-confirm { display: flex; flex-direction: column; gap: 6px; padding: 10px; border-radius: 9px;
|
|
1403
1485
|
border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 34%, var(--dsw-alias-border-l1));
|
|
1404
1486
|
background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 7%, transparent); }
|
|
@@ -1584,6 +1666,26 @@ var CSS = `
|
|
|
1584
1666
|
.dshsc-info-sec dd { margin: 0; color: var(--dsw-alias-label-primary); overflow-wrap: anywhere; }
|
|
1585
1667
|
.dshsc-info-sec dd.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; }
|
|
1586
1668
|
|
|
1669
|
+
/* 列表行的 ✨ 介绍入口:只占一个小胶囊,不改变行高与列宽 */
|
|
1670
|
+
.dshsc-intro-btn { flex: none; display: inline-flex; align-items: center; gap: 3px;
|
|
1671
|
+
height: 19px; padding: 0 7px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 999px;
|
|
1672
|
+
background: transparent; color: var(--dsw-alias-label-tertiary); font: inherit; font-size: 11px;
|
|
1673
|
+
line-height: 1; cursor: pointer; white-space: nowrap; }
|
|
1674
|
+
.dshsc-intro-btn:hover { color: var(--dsw-alias-label-primary); border-color: var(--dsw-alias-border-l3);
|
|
1675
|
+
background: var(--dsw-alias-bg-layer-1); }
|
|
1676
|
+
.dshsc-intro-btn:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }
|
|
1677
|
+
.dshsc-intro-btn .dshsc-spark { font-size: 10px; color: var(--dsw-alias-brand-primary); }
|
|
1678
|
+
|
|
1679
|
+
/* 介绍浮层:复用通用 floatpop 外壳(fixed + portal),只调内容排版 */
|
|
1680
|
+
.dshsc-intro-pop { width: min(360px, calc(100vw - 16px)); gap: 7px; }
|
|
1681
|
+
.dshsc-intro-text { margin: 0; font-size: 12.5px; line-height: 1.7; color: var(--dsw-alias-label-primary);
|
|
1682
|
+
white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
1683
|
+
.dshsc-intro-note { margin: 0; font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-tertiary); }
|
|
1684
|
+
.dshsc-intro-meta { margin: 0; font-size: 11px; line-height: 15px; color: var(--dsw-alias-label-tertiary); }
|
|
1685
|
+
.dshsc-intro-stale { margin: 0; font-size: 11px; line-height: 15px; color: var(--dsw-alias-state-warn-primary); }
|
|
1686
|
+
.dshsc-intro-error { margin: 0; font-size: 11.5px; line-height: 16px; color: var(--dsw-alias-state-error-primary); }
|
|
1687
|
+
.dshsc-intro-foot { display: flex; align-items: center; justify-content: flex-end; gap: 8px; margin-top: 3px; }
|
|
1688
|
+
|
|
1587
1689
|
/* 正文排版:只用 token,不写死深浅色 */
|
|
1588
1690
|
.dshsc-md { font-size: 13.5px; line-height: 1.72; color: var(--dsw-alias-label-primary); }
|
|
1589
1691
|
.dshsc-md-h { margin: 20px 0 8px; line-height: 1.35; font-weight: 650; letter-spacing: -.008em; }
|
|
@@ -2101,6 +2203,70 @@ function useSkillReader(row) {
|
|
|
2101
2203
|
}, []);
|
|
2102
2204
|
return { state, file, loadFile, retry };
|
|
2103
2205
|
}
|
|
2206
|
+
function useSkillIntro({ name, lang, stamp, enabled = true, endpoint = null }) {
|
|
2207
|
+
const base = endpoint || `/skills/${encodeURIComponent(name)}/intro`;
|
|
2208
|
+
const [state, setState] = import_react2.default.useState({ loading: true, intro: null, capability: null, error: null });
|
|
2209
|
+
const [generating, setGenerating] = import_react2.default.useState(false);
|
|
2210
|
+
const [genError, setGenError] = import_react2.default.useState(null);
|
|
2211
|
+
const seq = import_react2.default.useRef(0);
|
|
2212
|
+
const controller = import_react2.default.useRef(null);
|
|
2213
|
+
import_react2.default.useEffect(() => {
|
|
2214
|
+
if (!name || !enabled) {
|
|
2215
|
+
setState({ loading: false, intro: null, capability: null, error: null });
|
|
2216
|
+
return;
|
|
2217
|
+
}
|
|
2218
|
+
const ticket = seq.current + 1;
|
|
2219
|
+
seq.current = ticket;
|
|
2220
|
+
setState({ loading: true, intro: null, capability: null, error: null });
|
|
2221
|
+
void (async () => {
|
|
2222
|
+
try {
|
|
2223
|
+
const result = await apiCall("GET", `${base}?lang=${encodeURIComponent(lang || "zh")}`);
|
|
2224
|
+
if (ticket !== seq.current) return;
|
|
2225
|
+
setState({ loading: false, intro: result.intro || null, capability: result.capability || null, error: null });
|
|
2226
|
+
} catch (error) {
|
|
2227
|
+
if (ticket !== seq.current) return;
|
|
2228
|
+
setState({
|
|
2229
|
+
loading: false,
|
|
2230
|
+
intro: null,
|
|
2231
|
+
capability: null,
|
|
2232
|
+
error: { code: error?.code || null, message: error?.message || "读取失败" }
|
|
2233
|
+
});
|
|
2234
|
+
}
|
|
2235
|
+
})();
|
|
2236
|
+
}, [name, lang, stamp, enabled, base]);
|
|
2237
|
+
import_react2.default.useEffect(() => () => {
|
|
2238
|
+
if (controller.current) {
|
|
2239
|
+
controller.current.abort();
|
|
2240
|
+
controller.current = null;
|
|
2241
|
+
}
|
|
2242
|
+
}, [name, lang, base]);
|
|
2243
|
+
const generate = import_react2.default.useCallback(async (force = false) => {
|
|
2244
|
+
if (!name) return null;
|
|
2245
|
+
if (controller.current) return null;
|
|
2246
|
+
const ctrl = new AbortController();
|
|
2247
|
+
controller.current = ctrl;
|
|
2248
|
+
setGenerating(true);
|
|
2249
|
+
setGenError(null);
|
|
2250
|
+
try {
|
|
2251
|
+
const result = await apiCall(
|
|
2252
|
+
"POST",
|
|
2253
|
+
base,
|
|
2254
|
+
{ lang: lang || "zh", force: Boolean(force) },
|
|
2255
|
+
{ signal: ctrl.signal }
|
|
2256
|
+
);
|
|
2257
|
+
setState({ loading: false, intro: result.intro || null, capability: result.capability || null, error: null });
|
|
2258
|
+
return result;
|
|
2259
|
+
} catch (error) {
|
|
2260
|
+
if (error?.name === "AbortError") return null;
|
|
2261
|
+
setGenError({ code: error?.code || null, message: error?.message || "生成失败" });
|
|
2262
|
+
return null;
|
|
2263
|
+
} finally {
|
|
2264
|
+
if (controller.current === ctrl) controller.current = null;
|
|
2265
|
+
setGenerating(false);
|
|
2266
|
+
}
|
|
2267
|
+
}, [name, lang, base]);
|
|
2268
|
+
return { ...state, generating, genError, generate };
|
|
2269
|
+
}
|
|
2104
2270
|
|
|
2105
2271
|
// client/views/header.js
|
|
2106
2272
|
function installSpec(plugin) {
|
|
@@ -2294,31 +2460,75 @@ function Toolbar({
|
|
|
2294
2460
|
}
|
|
2295
2461
|
|
|
2296
2462
|
// client/views/installed.js
|
|
2297
|
-
var
|
|
2463
|
+
var import_react5 = __toESM(require("react"), 1);
|
|
2298
2464
|
|
|
2299
|
-
// client/views/
|
|
2465
|
+
// client/views/intro-popover.js
|
|
2300
2466
|
var import_react3 = __toESM(require("react"), 1);
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2467
|
+
var ERROR_KEY = {
|
|
2468
|
+
"error.skills.intro-no-model": "intro.error.noModel",
|
|
2469
|
+
"error.skills.intro-rate-limit": "intro.error.rateLimit",
|
|
2470
|
+
"error.skills.intro-quota": "intro.error.rateLimit",
|
|
2471
|
+
"error.skills.intro-too-large": "intro.error.tooLarge",
|
|
2472
|
+
"error.skills.intro-timeout": "intro.error.timeout",
|
|
2473
|
+
"error.skills.intro-auth": "intro.error.auth",
|
|
2474
|
+
"error.skills.intro-empty": "intro.error.empty",
|
|
2475
|
+
"error.skills.intro-busy": "intro.error.busy",
|
|
2476
|
+
"error.skills.intro-source": "intro.error.source",
|
|
2477
|
+
"error.skills.subscription-not-found": "intro.error.subscription",
|
|
2478
|
+
"error.skills.intro-source-timeout": "intro.error.sourceTimeout",
|
|
2479
|
+
"error.skills.intro-no-source": "intro.error.noSource"
|
|
2480
|
+
};
|
|
2481
|
+
function formatWhen(iso) {
|
|
2482
|
+
if (!iso) return null;
|
|
2483
|
+
const date = new Date(iso);
|
|
2484
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
2485
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
2486
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
2487
|
+
}
|
|
2488
|
+
function modelLabel(intro) {
|
|
2489
|
+
if (!intro?.model) return "—";
|
|
2490
|
+
return intro.provider ? `${intro.provider}/${intro.model}` : intro.model;
|
|
2491
|
+
}
|
|
2492
|
+
function IntroPopover({
|
|
2493
|
+
t,
|
|
2494
|
+
name,
|
|
2495
|
+
stamp = "",
|
|
2496
|
+
subtitle = null,
|
|
2497
|
+
endpoint = null,
|
|
2498
|
+
capability,
|
|
2499
|
+
anchor,
|
|
2500
|
+
anchorEl,
|
|
2501
|
+
hint = null,
|
|
2502
|
+
viewLabel = null,
|
|
2503
|
+
onClose,
|
|
2504
|
+
onView = null
|
|
2505
|
+
}) {
|
|
2305
2506
|
const ref = import_react3.default.useRef(null);
|
|
2306
2507
|
const [pos, setPos] = import_react3.default.useState({ left: 0, top: 0, visibility: "hidden" });
|
|
2508
|
+
const enabled = capability?.enabled !== false;
|
|
2509
|
+
const available = capability?.available !== false;
|
|
2510
|
+
const intro = useSkillIntro({
|
|
2511
|
+
name,
|
|
2512
|
+
lang: t.lang || "zh",
|
|
2513
|
+
stamp,
|
|
2514
|
+
enabled,
|
|
2515
|
+
endpoint
|
|
2516
|
+
});
|
|
2307
2517
|
useOverlayLayer(true, onClose);
|
|
2308
2518
|
import_react3.default.useLayoutEffect(() => {
|
|
2309
2519
|
const node = ref.current;
|
|
2310
2520
|
if (!node || !anchor || typeof window === "undefined") return;
|
|
2311
2521
|
const rect = node.getBoundingClientRect();
|
|
2312
2522
|
const width = rect.width || 360;
|
|
2313
|
-
const height = rect.height ||
|
|
2314
|
-
let left = anchor.left
|
|
2523
|
+
const height = rect.height || 220;
|
|
2524
|
+
let left = anchor.left;
|
|
2315
2525
|
left = Math.max(8, Math.min(left, window.innerWidth - width - 8));
|
|
2316
2526
|
let top = anchor.bottom + 8;
|
|
2317
2527
|
if (top + height > window.innerHeight - 8) {
|
|
2318
2528
|
top = Math.max(8, anchor.top - height - 8);
|
|
2319
2529
|
}
|
|
2320
2530
|
setPos({ left, top, visibility: "visible" });
|
|
2321
|
-
}, [anchor,
|
|
2531
|
+
}, [anchor, intro.loading, intro.intro?.text, intro.generating, intro.genError]);
|
|
2322
2532
|
import_react3.default.useEffect(() => {
|
|
2323
2533
|
if (typeof document === "undefined") return void 0;
|
|
2324
2534
|
const onPointerDown = (event) => {
|
|
@@ -2330,6 +2540,108 @@ function PolicyPopover({ t, row, anchor, anchorEl, busy, onClose, onSave, onUnpa
|
|
|
2330
2540
|
document.addEventListener("pointerdown", onPointerDown, true);
|
|
2331
2541
|
return () => document.removeEventListener("pointerdown", onPointerDown, true);
|
|
2332
2542
|
}, [anchorEl, onClose]);
|
|
2543
|
+
const failure = intro.genError || intro.error;
|
|
2544
|
+
const failureKey = failure?.code ? ERROR_KEY[failure.code] || "intro.error.generic" : null;
|
|
2545
|
+
const meta = intro.intro ? t("intro.meta").replace("{model}", modelLabel(intro.intro)).replace("{time}", formatWhen(intro.intro.generatedAt) || "—") : "";
|
|
2546
|
+
const actions = [];
|
|
2547
|
+
if (intro.intro) {
|
|
2548
|
+
actions.push(h("button", {
|
|
2549
|
+
type: "button",
|
|
2550
|
+
key: "regen",
|
|
2551
|
+
className: "dshsc-btn ghost",
|
|
2552
|
+
disabled: intro.generating || !available,
|
|
2553
|
+
onClick: () => {
|
|
2554
|
+
void intro.generate(true);
|
|
2555
|
+
}
|
|
2556
|
+
}, intro.generating ? t("intro.generating") : t("intro.regenerate")));
|
|
2557
|
+
if (onView) {
|
|
2558
|
+
actions.push(h("button", {
|
|
2559
|
+
type: "button",
|
|
2560
|
+
key: "view",
|
|
2561
|
+
className: "dshsc-btn primary",
|
|
2562
|
+
onClick: onView
|
|
2563
|
+
}, viewLabel || t("intro.view")));
|
|
2564
|
+
}
|
|
2565
|
+
} else {
|
|
2566
|
+
actions.push(h("button", {
|
|
2567
|
+
type: "button",
|
|
2568
|
+
key: "gen",
|
|
2569
|
+
className: "dshsc-btn primary",
|
|
2570
|
+
disabled: intro.generating || !available || !enabled,
|
|
2571
|
+
onClick: () => {
|
|
2572
|
+
void intro.generate(false);
|
|
2573
|
+
}
|
|
2574
|
+
}, intro.generating ? t("intro.generating") : t("intro.generate")));
|
|
2575
|
+
}
|
|
2576
|
+
return portalize(h(
|
|
2577
|
+
"div",
|
|
2578
|
+
{
|
|
2579
|
+
ref,
|
|
2580
|
+
className: "dshsc-floatpop dshsc-intro-pop",
|
|
2581
|
+
role: "dialog",
|
|
2582
|
+
"aria-label": `${t("intro.title")} · ${name}`,
|
|
2583
|
+
style: { left: `${pos.left}px`, top: `${pos.top}px`, visibility: pos.visibility }
|
|
2584
|
+
},
|
|
2585
|
+
h(
|
|
2586
|
+
"div",
|
|
2587
|
+
{ className: "dshsc-floatpop-head" },
|
|
2588
|
+
h(
|
|
2589
|
+
"div",
|
|
2590
|
+
null,
|
|
2591
|
+
h("div", { className: "dshsc-floatpop-title" }, `✨ ${t("intro.title")}`),
|
|
2592
|
+
h("div", { className: "dshsc-floatpop-sub", title: subtitle || name }, subtitle || name)
|
|
2593
|
+
),
|
|
2594
|
+
h("button", {
|
|
2595
|
+
type: "button",
|
|
2596
|
+
className: "dshsc-floatpop-close",
|
|
2597
|
+
"aria-label": t("action.close"),
|
|
2598
|
+
onClick: onClose
|
|
2599
|
+
}, "×")
|
|
2600
|
+
),
|
|
2601
|
+
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")),
|
|
2602
|
+
intro.intro?.stale ? h("p", { className: "dshsc-intro-stale" }, t("intro.stale")) : null,
|
|
2603
|
+
failureKey ? h("p", { className: "dshsc-intro-error" }, t(failureKey)) : null,
|
|
2604
|
+
meta ? h("p", { className: "dshsc-intro-meta" }, meta) : null,
|
|
2605
|
+
available && !intro.intro && !intro.loading ? h("p", { className: "dshsc-intro-meta" }, hint || t("intro.hint")) : null,
|
|
2606
|
+
!available ? h("p", { className: "dshsc-intro-error" }, t("intro.unavailable")) : null,
|
|
2607
|
+
h("div", { className: "dshsc-intro-foot" }, actions)
|
|
2608
|
+
));
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
// client/views/policy-modal.js
|
|
2612
|
+
var import_react4 = __toESM(require("react"), 1);
|
|
2613
|
+
|
|
2614
|
+
// client/views/overlay.js
|
|
2615
|
+
function Overlay({ t, title, children, footer, onCancel }) {
|
|
2616
|
+
useOverlayLayer(true, onCancel);
|
|
2617
|
+
return portalize(h(
|
|
2618
|
+
"div",
|
|
2619
|
+
{ className: "dshsc-overlay", onClick: (event) => {
|
|
2620
|
+
if (event.target === event.currentTarget) onCancel?.();
|
|
2621
|
+
} },
|
|
2622
|
+
h(
|
|
2623
|
+
"div",
|
|
2624
|
+
{ className: "dshsc-modal", role: "dialog", "aria-modal": "true" },
|
|
2625
|
+
h("button", {
|
|
2626
|
+
className: "dshsc-modal-close",
|
|
2627
|
+
"aria-label": t("action.cancel"),
|
|
2628
|
+
onClick: (event) => {
|
|
2629
|
+
event.stopPropagation();
|
|
2630
|
+
onCancel?.();
|
|
2631
|
+
}
|
|
2632
|
+
}, "×"),
|
|
2633
|
+
h("h3", null, title),
|
|
2634
|
+
children,
|
|
2635
|
+
h("div", { className: "dshsc-modal-foot" }, footer)
|
|
2636
|
+
)
|
|
2637
|
+
), overlayHost());
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
// client/views/policy-modal.js
|
|
2641
|
+
function PolicyModal({ t, row, busy, onClose, onSave, onUnpark }) {
|
|
2642
|
+
const initial = row.mode === "hidden" ? "auto" : row.mode;
|
|
2643
|
+
const [mode, setMode] = import_react4.default.useState(initial);
|
|
2644
|
+
const [confirming, setConfirming] = import_react4.default.useState(false);
|
|
2333
2645
|
const hidden = row.mode === "hidden";
|
|
2334
2646
|
const author = row.origin === "author" && row.mode === "manual";
|
|
2335
2647
|
const flat = row.kind === "file";
|
|
@@ -2351,7 +2663,8 @@ function PolicyPopover({ t, row, anchor, anchorEl, busy, onClose, onSave, onUnpa
|
|
|
2351
2663
|
type: "button",
|
|
2352
2664
|
key: `policy-${id}`,
|
|
2353
2665
|
className: `dshsc-policyopt${mode === id ? " active" : ""}`,
|
|
2354
|
-
"
|
|
2666
|
+
role: "radio",
|
|
2667
|
+
"aria-checked": String(mode === id),
|
|
2355
2668
|
disabled: busy || flat,
|
|
2356
2669
|
onClick: () => choose(id)
|
|
2357
2670
|
},
|
|
@@ -2363,95 +2676,85 @@ function PolicyPopover({ t, row, anchor, anchorEl, busy, onClose, onSave, onUnpa
|
|
|
2363
2676
|
h("small", null, desc)
|
|
2364
2677
|
)
|
|
2365
2678
|
);
|
|
2366
|
-
|
|
2367
|
-
"
|
|
2679
|
+
const footer = confirming ? [
|
|
2680
|
+
h("button", {
|
|
2681
|
+
type: "button",
|
|
2682
|
+
key: "cancel",
|
|
2683
|
+
className: "dshsc-btn ghost",
|
|
2684
|
+
disabled: busy,
|
|
2685
|
+
onClick: () => setConfirming(false)
|
|
2686
|
+
}, t("action.cancel")),
|
|
2687
|
+
h("button", {
|
|
2688
|
+
type: "button",
|
|
2689
|
+
key: "confirm",
|
|
2690
|
+
className: "dshsc-btn danger",
|
|
2691
|
+
disabled: busy,
|
|
2692
|
+
onClick: () => onSave("hidden")
|
|
2693
|
+
}, busy ? t("policy.saving") : t("policy.hiddenConfirm"))
|
|
2694
|
+
] : hidden ? [
|
|
2695
|
+
h("button", { type: "button", key: "cancel", className: "dshsc-btn ghost", onClick: onClose }, t("action.cancel")),
|
|
2696
|
+
h("button", {
|
|
2697
|
+
type: "button",
|
|
2698
|
+
key: "restore",
|
|
2699
|
+
className: "dshsc-btn primary",
|
|
2700
|
+
disabled: busy,
|
|
2701
|
+
onClick: onUnpark
|
|
2702
|
+
}, t("policy.unpark"))
|
|
2703
|
+
] : [
|
|
2704
|
+
h("button", { type: "button", key: "cancel", className: "dshsc-btn ghost", onClick: onClose }, t("action.cancel")),
|
|
2705
|
+
h("button", {
|
|
2706
|
+
type: "button",
|
|
2707
|
+
key: "save",
|
|
2708
|
+
className: "dshsc-btn primary",
|
|
2709
|
+
disabled: busy || !dirty || flat,
|
|
2710
|
+
onClick: submit
|
|
2711
|
+
}, busy ? t("policy.saving") : t("action.save"))
|
|
2712
|
+
];
|
|
2713
|
+
return h(
|
|
2714
|
+
Overlay,
|
|
2368
2715
|
{
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
style: { left: `${pos.left}px`, top: `${pos.top}px`, visibility: pos.visibility }
|
|
2716
|
+
t,
|
|
2717
|
+
title: t("policy.title"),
|
|
2718
|
+
onCancel: onClose,
|
|
2719
|
+
footer
|
|
2374
2720
|
},
|
|
2375
|
-
h(
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
"div",
|
|
2380
|
-
null,
|
|
2381
|
-
h("div", { className: "dshsc-policy-title" }, t("policy.title")),
|
|
2382
|
-
h("div", { className: "dshsc-policy-sub" }, t("policy.subtitle"))
|
|
2383
|
-
),
|
|
2384
|
-
h("button", {
|
|
2385
|
-
type: "button",
|
|
2386
|
-
className: "dshsc-policy-close",
|
|
2387
|
-
"aria-label": t("action.close"),
|
|
2388
|
-
onClick: onClose
|
|
2389
|
-
}, "×")
|
|
2390
|
-
),
|
|
2391
|
-
author ? h("div", { className: "dshsc-policy-note author" }, t("policy.authorHint")) : null,
|
|
2392
|
-
row.legacy ? h("div", { className: "dshsc-policy-note" }, t("policy.legacyHint")) : null,
|
|
2393
|
-
row.drift ? h("div", { className: "dshsc-policy-note warn" }, t("policy.driftHint")) : null,
|
|
2721
|
+
h("p", { className: "dshsc-sub", key: "sub" }, t("policy.subtitle")),
|
|
2722
|
+
author ? h("div", { className: "dshsc-policy-note author", key: "author" }, t("policy.authorHint")) : null,
|
|
2723
|
+
row.legacy ? h("div", { className: "dshsc-policy-note", key: "legacy" }, t("policy.legacyHint")) : null,
|
|
2724
|
+
row.drift ? h("div", { className: "dshsc-policy-note warn", key: "drift" }, t("policy.driftHint")) : null,
|
|
2394
2725
|
hidden ? h(
|
|
2395
2726
|
"div",
|
|
2396
|
-
{ className: "dshsc-policy-hidden" },
|
|
2727
|
+
{ className: "dshsc-policy-hidden", key: "hidden" },
|
|
2397
2728
|
h(
|
|
2398
2729
|
"div",
|
|
2399
|
-
{ className: "dshsc-
|
|
2400
|
-
h("span", { className: "dshsc-policyopt-dot" }),
|
|
2730
|
+
{ className: "dshsc-panel" },
|
|
2401
2731
|
h(
|
|
2402
|
-
"
|
|
2403
|
-
{ className: "dshsc-policyopt-
|
|
2404
|
-
h("
|
|
2405
|
-
h(
|
|
2732
|
+
"div",
|
|
2733
|
+
{ className: "dshsc-policyopt active static", role: "radio", "aria-checked": "true" },
|
|
2734
|
+
h("span", { className: "dshsc-policyopt-dot", "aria-hidden": "true" }),
|
|
2735
|
+
h(
|
|
2736
|
+
"span",
|
|
2737
|
+
{ className: "dshsc-policyopt-body" },
|
|
2738
|
+
h("b", null, t("policy.hidden")),
|
|
2739
|
+
h("small", null, t("policy.hiddenDesc"))
|
|
2740
|
+
)
|
|
2406
2741
|
)
|
|
2407
2742
|
),
|
|
2408
|
-
h("
|
|
2409
|
-
type: "button",
|
|
2410
|
-
className: "dshsc-btn primary dshsc-policy-restore",
|
|
2411
|
-
disabled: busy,
|
|
2412
|
-
onClick: onUnpark
|
|
2413
|
-
}, t("policy.unpark")),
|
|
2414
|
-
h("p", { className: "dshsc-policy-sub" }, t("policy.hiddenHint"))
|
|
2743
|
+
h("p", { className: "dshsc-sub" }, t("policy.hiddenHint"))
|
|
2415
2744
|
) : h(
|
|
2416
2745
|
"div",
|
|
2417
|
-
{ className: "dshsc-policy-options" },
|
|
2746
|
+
{ className: "dshsc-panel dshsc-policy-options", key: "options", role: "radiogroup", "aria-label": t("policy.title") },
|
|
2418
2747
|
option("auto", t("policy.auto"), t("policy.autoDesc")),
|
|
2419
2748
|
option("manual", t("policy.manual"), t("policy.manualDesc")),
|
|
2420
2749
|
option("hidden", t("policy.hidden"), t("policy.hiddenDesc"))
|
|
2421
2750
|
),
|
|
2422
2751
|
confirming ? h(
|
|
2423
2752
|
"div",
|
|
2424
|
-
{ className: "dshsc-policy-confirm" },
|
|
2753
|
+
{ className: "dshsc-policy-confirm", key: "confirm" },
|
|
2425
2754
|
h("b", null, t("policy.hiddenConfirmTitle")),
|
|
2426
|
-
h("p", null, t("policy.hiddenConfirmBody"))
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
{ className: "dshsc-policy-foot" },
|
|
2430
|
-
h("button", {
|
|
2431
|
-
type: "button",
|
|
2432
|
-
className: "dshsc-btn ghost",
|
|
2433
|
-
disabled: busy,
|
|
2434
|
-
onClick: () => setConfirming(false)
|
|
2435
|
-
}, t("action.cancel")),
|
|
2436
|
-
h("button", {
|
|
2437
|
-
type: "button",
|
|
2438
|
-
className: "dshsc-btn danger",
|
|
2439
|
-
disabled: busy,
|
|
2440
|
-
onClick: () => onSave("hidden")
|
|
2441
|
-
}, busy ? t("policy.saving") : t("policy.hiddenConfirm"))
|
|
2442
|
-
)
|
|
2443
|
-
) : h(
|
|
2444
|
-
"div",
|
|
2445
|
-
{ className: "dshsc-policy-foot" },
|
|
2446
|
-
h("button", { type: "button", className: "dshsc-btn ghost", onClick: onClose }, t("action.cancel")),
|
|
2447
|
-
hidden ? null : h("button", {
|
|
2448
|
-
type: "button",
|
|
2449
|
-
className: "dshsc-btn primary",
|
|
2450
|
-
disabled: busy || !dirty || flat,
|
|
2451
|
-
onClick: submit
|
|
2452
|
-
}, busy ? t("policy.saving") : t("action.save"))
|
|
2453
|
-
)
|
|
2454
|
-
), overlayHost());
|
|
2755
|
+
h("p", null, t("policy.hiddenConfirmBody"))
|
|
2756
|
+
) : null
|
|
2757
|
+
);
|
|
2455
2758
|
}
|
|
2456
2759
|
|
|
2457
2760
|
// client/views/installed.js
|
|
@@ -2473,24 +2776,33 @@ function InstalledTab({
|
|
|
2473
2776
|
actions
|
|
2474
2777
|
}) {
|
|
2475
2778
|
const updatable = (row) => UPDATABLE.has(row.status);
|
|
2476
|
-
const [policy, setPolicy] =
|
|
2779
|
+
const [policy, setPolicy] = import_react5.default.useState(null);
|
|
2780
|
+
const [intro, setIntro] = import_react5.default.useState(null);
|
|
2477
2781
|
const sharedTargets = new Set((data?.agents?.detected || []).filter((agent) => agent.kind === "agent" && agent.sharedHome === "yes").map((agent) => agent.target));
|
|
2478
|
-
|
|
2782
|
+
import_react5.default.useEffect(() => {
|
|
2479
2783
|
if (!policy) return;
|
|
2480
2784
|
if (rows.some((row) => row.name === policy.row.name)) return;
|
|
2481
2785
|
setPolicy(null);
|
|
2482
2786
|
}, [policy, rows]);
|
|
2483
|
-
|
|
2484
|
-
if (
|
|
2485
|
-
if (row
|
|
2787
|
+
import_react5.default.useEffect(() => {
|
|
2788
|
+
if (!intro) return;
|
|
2789
|
+
if (rows.some((row) => row.name === intro.row.name)) return;
|
|
2790
|
+
setIntro(null);
|
|
2791
|
+
}, [intro, rows]);
|
|
2792
|
+
function openIntro(event, row) {
|
|
2486
2793
|
const el = event.currentTarget;
|
|
2487
2794
|
const rect = el.getBoundingClientRect();
|
|
2488
|
-
|
|
2795
|
+
setIntro({
|
|
2489
2796
|
row,
|
|
2490
2797
|
anchorEl: el,
|
|
2491
2798
|
anchor: { left: rect.left, top: rect.top, bottom: rect.bottom, width: rect.width, height: rect.height }
|
|
2492
2799
|
});
|
|
2493
2800
|
}
|
|
2801
|
+
function openPolicy(row) {
|
|
2802
|
+
if (busy !== null || row.kind === "file") return;
|
|
2803
|
+
if (row.mode === "manual" && row.origin === "author") return;
|
|
2804
|
+
setPolicy({ row });
|
|
2805
|
+
}
|
|
2494
2806
|
function policyChip(row) {
|
|
2495
2807
|
if (row.status === "unmanaged") {
|
|
2496
2808
|
return h("span", { className: "dshsc-chip muted", title: t("adopt.switchHint") }, t("policy.unmanaged"));
|
|
@@ -2506,7 +2818,7 @@ function InstalledTab({
|
|
|
2506
2818
|
disabled: busy !== null,
|
|
2507
2819
|
title: t("policy.hiddenHint"),
|
|
2508
2820
|
"aria-haspopup": "dialog",
|
|
2509
|
-
onClick: (
|
|
2821
|
+
onClick: () => openPolicy(row)
|
|
2510
2822
|
}, t("policy.hidden"));
|
|
2511
2823
|
}
|
|
2512
2824
|
if (mode === "manual" && row.origin === "author") {
|
|
@@ -2522,7 +2834,7 @@ function InstalledTab({
|
|
|
2522
2834
|
disabled: busy !== null,
|
|
2523
2835
|
title: t("policy.driftHint"),
|
|
2524
2836
|
"aria-haspopup": "dialog",
|
|
2525
|
-
onClick: (
|
|
2837
|
+
onClick: () => openPolicy(row)
|
|
2526
2838
|
}, t("policy.drift"));
|
|
2527
2839
|
}
|
|
2528
2840
|
if (row.legacy === "dsh-off") {
|
|
@@ -2532,7 +2844,7 @@ function InstalledTab({
|
|
|
2532
2844
|
disabled: busy !== null,
|
|
2533
2845
|
title: t("policy.legacyHint"),
|
|
2534
2846
|
"aria-haspopup": "dialog",
|
|
2535
|
-
onClick: (
|
|
2847
|
+
onClick: () => openPolicy(row)
|
|
2536
2848
|
}, t("policy.legacy"));
|
|
2537
2849
|
}
|
|
2538
2850
|
if (mode === "manual") {
|
|
@@ -2542,7 +2854,7 @@ function InstalledTab({
|
|
|
2542
2854
|
disabled: busy !== null,
|
|
2543
2855
|
title: t("policy.manualOnlyHint"),
|
|
2544
2856
|
"aria-haspopup": "dialog",
|
|
2545
|
-
onClick: (
|
|
2857
|
+
onClick: () => openPolicy(row)
|
|
2546
2858
|
}, t("policy.manual"));
|
|
2547
2859
|
}
|
|
2548
2860
|
return h("button", {
|
|
@@ -2551,7 +2863,7 @@ function InstalledTab({
|
|
|
2551
2863
|
disabled: busy !== null,
|
|
2552
2864
|
title: t("policy.open"),
|
|
2553
2865
|
"aria-haspopup": "dialog",
|
|
2554
|
-
onClick: (
|
|
2866
|
+
onClick: () => openPolicy(row)
|
|
2555
2867
|
}, t("policy.auto"));
|
|
2556
2868
|
}
|
|
2557
2869
|
function statusTag(row) {
|
|
@@ -2673,6 +2985,18 @@ function InstalledTab({
|
|
|
2673
2985
|
"aria-haspopup": "dialog",
|
|
2674
2986
|
onClick: () => actions.onView(row)
|
|
2675
2987
|
}, row.name),
|
|
2988
|
+
data?.intro ? h(
|
|
2989
|
+
"button",
|
|
2990
|
+
{
|
|
2991
|
+
type: "button",
|
|
2992
|
+
className: "dshsc-intro-btn",
|
|
2993
|
+
title: t("intro.open"),
|
|
2994
|
+
"aria-haspopup": "dialog",
|
|
2995
|
+
onClick: (event) => openIntro(event, row)
|
|
2996
|
+
},
|
|
2997
|
+
h("span", { className: "dshsc-spark", "aria-hidden": "true" }, "✨"),
|
|
2998
|
+
t("intro.button")
|
|
2999
|
+
) : null,
|
|
2676
3000
|
statusTag(row)
|
|
2677
3001
|
),
|
|
2678
3002
|
sourceLine(row),
|
|
@@ -2786,6 +3110,7 @@ function InstalledTab({
|
|
|
2786
3110
|
}, dot, h("span", null, label), h("span", { className: "n" }, counts[id]));
|
|
2787
3111
|
}
|
|
2788
3112
|
function listBar() {
|
|
3113
|
+
const updateDot = counts.update > 0 ? h("i", { className: "dshsc-fdot" }) : null;
|
|
2789
3114
|
return h(
|
|
2790
3115
|
"div",
|
|
2791
3116
|
{ className: "dshsc-listbar" },
|
|
@@ -2793,7 +3118,7 @@ function InstalledTab({
|
|
|
2793
3118
|
"div",
|
|
2794
3119
|
{ className: "dshsc-filters" },
|
|
2795
3120
|
filterChip("all", t("filter.all")),
|
|
2796
|
-
filterChip("update", t("filter.update"),
|
|
3121
|
+
filterChip("update", t("filter.update"), updateDot),
|
|
2797
3122
|
filterChip("unknown", t("filter.unknown")),
|
|
2798
3123
|
filterChip("manual", t("filter.manual")),
|
|
2799
3124
|
filterChip("off", t("filter.off"))
|
|
@@ -2921,11 +3246,23 @@ function InstalledTab({
|
|
|
2921
3246
|
)
|
|
2922
3247
|
);
|
|
2923
3248
|
}
|
|
2924
|
-
const
|
|
3249
|
+
const introPopover = intro && data?.intro ? h(IntroPopover, {
|
|
3250
|
+
t,
|
|
3251
|
+
name: intro.row.name,
|
|
3252
|
+
stamp: [intro.row.commit || "", intro.row.currentHash || "", intro.row.updatedAt || "", intro.row.status || ""].join("|"),
|
|
3253
|
+
anchor: intro.anchor,
|
|
3254
|
+
anchorEl: intro.anchorEl,
|
|
3255
|
+
capability: data.intro,
|
|
3256
|
+
onClose: () => setIntro(null),
|
|
3257
|
+
onView: () => {
|
|
3258
|
+
const target = intro.row;
|
|
3259
|
+
setIntro(null);
|
|
3260
|
+
actions.onView(target);
|
|
3261
|
+
}
|
|
3262
|
+
}) : null;
|
|
3263
|
+
const policyModal = policy ? h(PolicyModal, {
|
|
2925
3264
|
t,
|
|
2926
3265
|
row: policy.row,
|
|
2927
|
-
anchor: policy.anchor,
|
|
2928
|
-
anchorEl: policy.anchorEl,
|
|
2929
3266
|
busy: busy !== null,
|
|
2930
3267
|
onClose: () => setPolicy(null),
|
|
2931
3268
|
onSave: (mode) => {
|
|
@@ -2939,7 +3276,7 @@ function InstalledTab({
|
|
|
2939
3276
|
actions.onRequestUnpark(target);
|
|
2940
3277
|
}
|
|
2941
3278
|
}) : null;
|
|
2942
|
-
return h(
|
|
3279
|
+
return h(import_react5.default.Fragment, null, content(), policyModal, introPopover);
|
|
2943
3280
|
}
|
|
2944
3281
|
|
|
2945
3282
|
// client/views/agent-tip.js
|
|
@@ -2974,10 +3311,14 @@ function AgentTip({ t, agentTip, agentTipPos, nodeRef }) {
|
|
|
2974
3311
|
}
|
|
2975
3312
|
|
|
2976
3313
|
// client/views/subscriptions.js
|
|
3314
|
+
var import_react6 = __toESM(require("react"), 1);
|
|
2977
3315
|
function repoOf(source) {
|
|
2978
3316
|
const value = String(source || "").trim().toLowerCase().replace(/\.git$/, "");
|
|
2979
3317
|
return value || null;
|
|
2980
3318
|
}
|
|
3319
|
+
function shortCommit(value) {
|
|
3320
|
+
return typeof value === "string" ? value.slice(0, 7) : "";
|
|
3321
|
+
}
|
|
2981
3322
|
function SubscriptionsTab({
|
|
2982
3323
|
t,
|
|
2983
3324
|
subscriptions,
|
|
@@ -2985,12 +3326,72 @@ function SubscriptionsTab({
|
|
|
2985
3326
|
expanded,
|
|
2986
3327
|
busy,
|
|
2987
3328
|
writable,
|
|
3329
|
+
capability = null,
|
|
2988
3330
|
onToggle,
|
|
2989
3331
|
onRefresh,
|
|
2990
3332
|
onDelete,
|
|
2991
3333
|
onInstall,
|
|
2992
3334
|
onAdd
|
|
2993
3335
|
}) {
|
|
3336
|
+
const [intro, setIntro] = import_react6.default.useState(null);
|
|
3337
|
+
import_react6.default.useEffect(() => {
|
|
3338
|
+
if (!intro) return;
|
|
3339
|
+
const subscription = subscriptions.find((item) => item.id === intro.subscriptionId);
|
|
3340
|
+
const entries = subscription?.snapshot?.entries || [];
|
|
3341
|
+
if (entries.some((entry) => entry.name === intro.name)) return;
|
|
3342
|
+
setIntro(null);
|
|
3343
|
+
}, [intro, subscriptions]);
|
|
3344
|
+
function openIntro(event, subscription, entry) {
|
|
3345
|
+
const el = event.currentTarget;
|
|
3346
|
+
const rect = el.getBoundingClientRect();
|
|
3347
|
+
setIntro({
|
|
3348
|
+
subscriptionId: subscription.id,
|
|
3349
|
+
name: entry.name,
|
|
3350
|
+
anchorEl: el,
|
|
3351
|
+
anchor: { left: rect.left, top: rect.top, bottom: rect.bottom, width: rect.width, height: rect.height }
|
|
3352
|
+
});
|
|
3353
|
+
}
|
|
3354
|
+
function introButton(subscription, entry) {
|
|
3355
|
+
return h(
|
|
3356
|
+
"button",
|
|
3357
|
+
{
|
|
3358
|
+
type: "button",
|
|
3359
|
+
className: "dshsc-intro-btn",
|
|
3360
|
+
title: t("intro.open"),
|
|
3361
|
+
"aria-haspopup": "dialog",
|
|
3362
|
+
onClick: (clickEvent) => openIntro(clickEvent, subscription, entry)
|
|
3363
|
+
},
|
|
3364
|
+
h("span", { className: "dshsc-spark", "aria-hidden": "true" }, "✨"),
|
|
3365
|
+
t("intro.button")
|
|
3366
|
+
);
|
|
3367
|
+
}
|
|
3368
|
+
const introSubscription = intro ? subscriptions.find((item) => item.id === intro.subscriptionId) || null : null;
|
|
3369
|
+
const introEntry = introSubscription ? (introSubscription.snapshot?.entries || []).find((entry) => entry.name === intro.name) || null : null;
|
|
3370
|
+
const introPopover = intro && introEntry && capability ? (() => {
|
|
3371
|
+
const commit = introSubscription.snapshot?.commit || "";
|
|
3372
|
+
const installedAlready = rows.some((row) => {
|
|
3373
|
+
if (row.name !== introEntry.name) return false;
|
|
3374
|
+
if (row.subscriptionId === introSubscription.id) return true;
|
|
3375
|
+
return repoOf(row.source) === repoOf(introSubscription.source);
|
|
3376
|
+
});
|
|
3377
|
+
return h(IntroPopover, {
|
|
3378
|
+
t,
|
|
3379
|
+
name: introEntry.name,
|
|
3380
|
+
stamp: commit,
|
|
3381
|
+
subtitle: `${introSubscription.source}@${shortCommit(commit)}`,
|
|
3382
|
+
endpoint: `/subscriptions/${encodeURIComponent(introSubscription.id)}/entries/${encodeURIComponent(introEntry.name)}/intro`,
|
|
3383
|
+
capability,
|
|
3384
|
+
anchor: intro.anchor,
|
|
3385
|
+
anchorEl: intro.anchorEl,
|
|
3386
|
+
hint: t("intro.hintRemote").replace("{source}", introSubscription.source).replace("{commit}", shortCommit(commit) || "—"),
|
|
3387
|
+
viewLabel: t("action.install"),
|
|
3388
|
+
onView: installedAlready ? null : () => {
|
|
3389
|
+
setIntro(null);
|
|
3390
|
+
void onInstall(introSubscription, introEntry);
|
|
3391
|
+
},
|
|
3392
|
+
onClose: () => setIntro(null)
|
|
3393
|
+
});
|
|
3394
|
+
})() : null;
|
|
2994
3395
|
if (subscriptions.length === 0) {
|
|
2995
3396
|
return h(
|
|
2996
3397
|
"div",
|
|
@@ -3013,154 +3414,140 @@ function SubscriptionsTab({
|
|
|
3013
3414
|
);
|
|
3014
3415
|
}
|
|
3015
3416
|
return h(
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
// 表头与仓库行共用同一个滚动容器,位置 sticky;宽屏 4 列,窄屏收成「仓库」/「操作」。
|
|
3417
|
+
import_react6.default.Fragment,
|
|
3418
|
+
null,
|
|
3019
3419
|
h(
|
|
3020
3420
|
"div",
|
|
3021
|
-
{ className: "dshsc-
|
|
3421
|
+
{ className: "dshsc-tabpane" },
|
|
3422
|
+
// 表头与仓库行共用同一个滚动容器,位置 sticky;宽屏 4 列,窄屏收成「仓库」/「操作」。
|
|
3022
3423
|
h(
|
|
3023
3424
|
"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(
|
|
3425
|
+
{ className: "dshsc-listscroll dshsc-subscroll" },
|
|
3426
|
+
h(
|
|
3043
3427
|
"div",
|
|
3044
|
-
{ className: "dshsc-
|
|
3045
|
-
h(
|
|
3428
|
+
{ className: "dshsc-subhead" },
|
|
3429
|
+
h("div", { className: "dshsc-hrepo" }, t("column.repo")),
|
|
3430
|
+
h("div", { className: "dshsc-href" }, t("column.ref")),
|
|
3431
|
+
h("div", { className: "dshsc-hcount" }, t("column.entries")),
|
|
3432
|
+
h("div", { className: "dshsc-hact" }, t("column.actions"))
|
|
3433
|
+
),
|
|
3434
|
+
subscriptions.map((subscription) => {
|
|
3435
|
+
const entries = subscription.snapshot?.entries || [];
|
|
3436
|
+
const repo = repoOf(subscription.source);
|
|
3437
|
+
const rowsByName = new Map(rows.map((row) => [row.name, row]));
|
|
3438
|
+
const installedRow = (name) => {
|
|
3439
|
+
const row = rowsByName.get(name);
|
|
3440
|
+
if (!row) return null;
|
|
3441
|
+
if (row.subscriptionId === subscription.id) return row;
|
|
3442
|
+
return repo && repoOf(row.source) === repo ? row : null;
|
|
3443
|
+
};
|
|
3444
|
+
const installedCount = entries.filter((entry) => installedRow(entry.name)).length;
|
|
3445
|
+
const open = Boolean(expanded[subscription.id]);
|
|
3446
|
+
return h(
|
|
3046
3447
|
"div",
|
|
3047
|
-
{ className: "dshsc-sub-
|
|
3048
|
-
h(
|
|
3049
|
-
"div",
|
|
3050
|
-
{ className: "dshsc-sub-repo" },
|
|
3051
|
-
h("button", {
|
|
3052
|
-
className: "dshsc-caret",
|
|
3053
|
-
"aria-expanded": String(open),
|
|
3054
|
-
"aria-label": `${subscription.source} ${open ? t("action.collapse") : t("action.expand")}`,
|
|
3055
|
-
onClick: () => onToggle(subscription.id)
|
|
3056
|
-
}, h(CaretIcon, { open })),
|
|
3057
|
-
h("span", { className: "dshsc-name" }, subscription.source)
|
|
3058
|
-
),
|
|
3059
|
-
// 宽屏铺成 Ref / 条目两列;窄屏回到仓库名下方一行。
|
|
3448
|
+
{ className: "dshsc-sub-block", key: subscription.id },
|
|
3060
3449
|
h(
|
|
3061
3450
|
"div",
|
|
3062
|
-
{ className: "dshsc-sub-
|
|
3063
|
-
h(
|
|
3451
|
+
{ className: "dshsc-sub-row" },
|
|
3452
|
+
h(
|
|
3453
|
+
"div",
|
|
3454
|
+
{ className: "dshsc-sub-repo" },
|
|
3455
|
+
h("button", {
|
|
3456
|
+
className: "dshsc-caret",
|
|
3457
|
+
"aria-expanded": String(open),
|
|
3458
|
+
"aria-label": `${subscription.source} ${open ? t("action.collapse") : t("action.expand")}`,
|
|
3459
|
+
onClick: () => onToggle(subscription.id)
|
|
3460
|
+
}, h(CaretIcon, { open })),
|
|
3461
|
+
h("span", { className: "dshsc-name" }, subscription.source)
|
|
3462
|
+
),
|
|
3463
|
+
// 宽屏铺成 Ref / 条目两列;窄屏回到仓库名下方一行。
|
|
3064
3464
|
h(
|
|
3065
|
-
"
|
|
3066
|
-
{ className: "dshsc-
|
|
3067
|
-
|
|
3465
|
+
"div",
|
|
3466
|
+
{ className: "dshsc-sub-meta" },
|
|
3467
|
+
h("span", { className: "dshsc-tag dshsc-subref" }, subscription.ref || t("subscription.defaultRef")),
|
|
3468
|
+
h(
|
|
3469
|
+
"span",
|
|
3470
|
+
{ className: "dshsc-substat" },
|
|
3471
|
+
`${entries.length} ${t("skillCount")} · ${t("installed")} ${installedCount}`
|
|
3472
|
+
)
|
|
3473
|
+
),
|
|
3474
|
+
h(
|
|
3475
|
+
"div",
|
|
3476
|
+
{ className: "dshsc-actions" },
|
|
3477
|
+
h("button", {
|
|
3478
|
+
className: "dshsc-btn sq",
|
|
3479
|
+
disabled: busy !== null,
|
|
3480
|
+
title: t("action.checkUpdates"),
|
|
3481
|
+
"aria-label": `${t("action.checkUpdates")} ${subscription.source}`,
|
|
3482
|
+
onClick: () => void onRefresh(subscription)
|
|
3483
|
+
}, h(RefreshIcon, { size: 13 })),
|
|
3484
|
+
h("button", { className: "dshsc-btn ghost danger", disabled: busy !== null, onClick: () => void onDelete(subscription) }, t("action.unsubscribe"))
|
|
3068
3485
|
)
|
|
3069
3486
|
),
|
|
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(
|
|
3487
|
+
open ? h(
|
|
3087
3488
|
"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(
|
|
3489
|
+
{ className: "dshsc-sub-list" },
|
|
3490
|
+
h(
|
|
3096
3491
|
"div",
|
|
3097
|
-
{ className: "dshsc-
|
|
3098
|
-
h("
|
|
3099
|
-
h(
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
h(
|
|
3492
|
+
{ className: "dshsc-entryhead" },
|
|
3493
|
+
h("div", null, t("column.skill")),
|
|
3494
|
+
h("div", null, t("column.status")),
|
|
3495
|
+
h("div", { className: "right" }, t("column.actions"))
|
|
3496
|
+
),
|
|
3497
|
+
entries.map((entry) => {
|
|
3498
|
+
const installedAlready = Boolean(installedRow(entry.name));
|
|
3499
|
+
return h(
|
|
3106
3500
|
"div",
|
|
3107
|
-
{ className: "dshsc-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3501
|
+
{ className: "dshsc-sub-entry", key: entry.name },
|
|
3502
|
+
h(
|
|
3503
|
+
"div",
|
|
3504
|
+
{ className: "dshsc-sub-skill" },
|
|
3505
|
+
h(
|
|
3506
|
+
"div",
|
|
3507
|
+
{ className: "dshsc-sub-namerow" },
|
|
3508
|
+
h("span", { className: "dshsc-name" }, entry.name),
|
|
3509
|
+
capability ? introButton(subscription, entry) : null
|
|
3510
|
+
),
|
|
3511
|
+
entry.description ? h("div", { className: "dshsc-sub-desc", title: entry.description }, entry.description) : null
|
|
3512
|
+
),
|
|
3513
|
+
h(
|
|
3514
|
+
"span",
|
|
3515
|
+
{ className: `dshsc-state${installedAlready ? " ok" : ""}` },
|
|
3516
|
+
installedAlready ? t("installed") : t("notInstalled")
|
|
3517
|
+
),
|
|
3518
|
+
h(
|
|
3519
|
+
"div",
|
|
3520
|
+
{ className: "dshsc-actions" },
|
|
3521
|
+
installedAlready ? h("span", { className: "dshsc-noop" }, "-") : h("button", {
|
|
3522
|
+
className: "dshsc-btn",
|
|
3523
|
+
disabled: !writable || busy !== null,
|
|
3524
|
+
onClick: () => void onInstall(subscription, entry)
|
|
3525
|
+
}, t("action.install"))
|
|
3526
|
+
)
|
|
3527
|
+
);
|
|
3528
|
+
})
|
|
3529
|
+
) : null
|
|
3530
|
+
);
|
|
3531
|
+
})
|
|
3532
|
+
)
|
|
3533
|
+
),
|
|
3534
|
+
introPopover
|
|
3120
3535
|
);
|
|
3121
3536
|
}
|
|
3122
3537
|
|
|
3123
3538
|
// client/views/modals.js
|
|
3124
|
-
var
|
|
3125
|
-
|
|
3126
|
-
// client/views/add-skill.js
|
|
3127
|
-
var import_react5 = __toESM(require("react"), 1);
|
|
3128
|
-
|
|
3129
|
-
// client/views/overlay.js
|
|
3130
|
-
function Overlay({ t, title, children, footer, onCancel }) {
|
|
3131
|
-
useOverlayLayer(true, onCancel);
|
|
3132
|
-
return portalize(h(
|
|
3133
|
-
"div",
|
|
3134
|
-
{ className: "dshsc-overlay", onClick: (event) => {
|
|
3135
|
-
if (event.target === event.currentTarget) onCancel?.();
|
|
3136
|
-
} },
|
|
3137
|
-
h(
|
|
3138
|
-
"div",
|
|
3139
|
-
{ className: "dshsc-modal", role: "dialog", "aria-modal": "true" },
|
|
3140
|
-
h("button", {
|
|
3141
|
-
className: "dshsc-modal-close",
|
|
3142
|
-
"aria-label": t("action.cancel"),
|
|
3143
|
-
onClick: (event) => {
|
|
3144
|
-
event.stopPropagation();
|
|
3145
|
-
onCancel?.();
|
|
3146
|
-
}
|
|
3147
|
-
}, "×"),
|
|
3148
|
-
h("h3", null, title),
|
|
3149
|
-
children,
|
|
3150
|
-
h("div", { className: "dshsc-modal-foot" }, footer)
|
|
3151
|
-
)
|
|
3152
|
-
), overlayHost());
|
|
3153
|
-
}
|
|
3539
|
+
var import_react8 = __toESM(require("react"), 1);
|
|
3154
3540
|
|
|
3155
3541
|
// client/views/add-skill.js
|
|
3542
|
+
var import_react7 = __toESM(require("react"), 1);
|
|
3156
3543
|
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] =
|
|
3544
|
+
const [source, setSource] = import_react7.default.useState("");
|
|
3545
|
+
const [phase, setPhase] = import_react7.default.useState("idle");
|
|
3546
|
+
const [options, setOptions] = import_react7.default.useState([]);
|
|
3547
|
+
const [selected, setSelected] = import_react7.default.useState(() => /* @__PURE__ */ new Set());
|
|
3548
|
+
const [manual, setManual] = import_react7.default.useState("");
|
|
3549
|
+
const [skipped, setSkipped] = import_react7.default.useState(0);
|
|
3550
|
+
const [error, setError] = import_react7.default.useState(null);
|
|
3164
3551
|
const reset = (value) => {
|
|
3165
3552
|
setSource(value);
|
|
3166
3553
|
setPhase("idle");
|
|
@@ -3296,7 +3683,7 @@ function AddSkillModal({ t, onCancel, onProbe, onInstall }) {
|
|
|
3296
3683
|
|
|
3297
3684
|
// client/views/modals.js
|
|
3298
3685
|
function FormModal({ t, title, hint, fields, onCancel, onSubmit }) {
|
|
3299
|
-
const [form, setForm] =
|
|
3686
|
+
const [form, setForm] = import_react8.default.useState(() => Object.fromEntries(fields.map((field) => [field.key, ""])));
|
|
3300
3687
|
const incomplete = fields.some(
|
|
3301
3688
|
(field) => field.required !== false && !String(form[field.key] || "").trim()
|
|
3302
3689
|
);
|
|
@@ -3344,7 +3731,7 @@ function ExportsModal({ t, row, agents, onCancel, onSave }) {
|
|
|
3344
3731
|
const candidates = agents.filter(
|
|
3345
3732
|
(agent) => agent.kind === "agent" && agent.exists && agent.exportable
|
|
3346
3733
|
);
|
|
3347
|
-
const [targets, setTargets] =
|
|
3734
|
+
const [targets, setTargets] = import_react8.default.useState(() => {
|
|
3348
3735
|
const available = new Set(candidates.map((agent) => agent.target));
|
|
3349
3736
|
return new Set((row.exports || []).map((item) => item.target).filter((target) => available.has(target)));
|
|
3350
3737
|
});
|
|
@@ -3386,7 +3773,7 @@ function ExportsModal({ t, row, agents, onCancel, onSave }) {
|
|
|
3386
3773
|
);
|
|
3387
3774
|
}
|
|
3388
3775
|
function DefaultExportModal({ t, settings, agents, onCancel, onSave }) {
|
|
3389
|
-
const [targets, setTargets] =
|
|
3776
|
+
const [targets, setTargets] = import_react8.default.useState(() => new Set(
|
|
3390
3777
|
Array.isArray(settings.exportTargets) ? settings.exportTargets : []
|
|
3391
3778
|
));
|
|
3392
3779
|
const toggle = (target) => setTargets((prev) => {
|
|
@@ -3515,7 +3902,7 @@ ${conflicts.map((item) => `${item.label}: ${item.linkPath}`).join("\n")}`,
|
|
|
3515
3902
|
}
|
|
3516
3903
|
|
|
3517
3904
|
// client/views/skill-reader.js
|
|
3518
|
-
var
|
|
3905
|
+
var import_react9 = __toESM(require("react"), 1);
|
|
3519
3906
|
|
|
3520
3907
|
// client/markdown.js
|
|
3521
3908
|
function classifyHref(href) {
|
|
@@ -3951,7 +4338,7 @@ function formatBytes(bytes) {
|
|
|
3951
4338
|
if (value < 1024 * 1024) return `${(value / 1024).toFixed(value < 10 * 1024 ? 1 : 0)} KB`;
|
|
3952
4339
|
return `${(value / (1024 * 1024)).toFixed(1)} MB`;
|
|
3953
4340
|
}
|
|
3954
|
-
function
|
|
4341
|
+
function formatWhen2(iso) {
|
|
3955
4342
|
if (!iso) return null;
|
|
3956
4343
|
const date = new Date(iso);
|
|
3957
4344
|
if (Number.isNaN(date.getTime())) return null;
|
|
@@ -4011,37 +4398,37 @@ function SkillReader({
|
|
|
4011
4398
|
onToast
|
|
4012
4399
|
}) {
|
|
4013
4400
|
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
4401
|
const name = row?.name || "";
|
|
4402
|
+
const [tab, setTab] = import_react9.default.useState(initialTab);
|
|
4403
|
+
const [format, setFormat] = import_react9.default.useState("rendered");
|
|
4404
|
+
const [docOverride, setDocOverride] = import_react9.default.useState(null);
|
|
4405
|
+
const [fileFormat, setFileFormat] = import_react9.default.useState("rendered");
|
|
4406
|
+
const [tocActive, setTocActive] = import_react9.default.useState(null);
|
|
4407
|
+
const [descOpen, setDescOpen] = import_react9.default.useState(false);
|
|
4408
|
+
const [descClamped, setDescClamped] = import_react9.default.useState(false);
|
|
4409
|
+
const bodyRef = import_react9.default.useRef(null);
|
|
4410
|
+
const dialogRef = import_react9.default.useRef(null);
|
|
4411
|
+
const descRef = import_react9.default.useRef(null);
|
|
4412
|
+
const scrollRef = import_react9.default.useRef(0);
|
|
4413
|
+
useOverlayLayer(variant === "drawer" || variant === "page", onClose);
|
|
4027
4414
|
const tree = state.tree;
|
|
4028
4415
|
const files = tree?.files || [];
|
|
4029
4416
|
const fileCount = files.filter((entry) => entry.kind === "file").length;
|
|
4030
4417
|
const bodyText = docOverride ? docOverride.content : state.body?.content || "";
|
|
4031
4418
|
const description = row?.description || state.body?.meta?.description || "";
|
|
4032
4419
|
const headings = tab === "doc" && bodyText ? parseCached(bodyText).headings : [];
|
|
4033
|
-
|
|
4420
|
+
import_react9.default.useEffect(() => {
|
|
4034
4421
|
setDocOverride(null);
|
|
4035
4422
|
scrollRef.current = 0;
|
|
4036
4423
|
}, [name]);
|
|
4037
|
-
const descOwnerRef =
|
|
4038
|
-
|
|
4424
|
+
const descOwnerRef = import_react9.default.useRef(name);
|
|
4425
|
+
import_react9.default.useEffect(() => {
|
|
4039
4426
|
if (descOwnerRef.current === name) return;
|
|
4040
4427
|
descOwnerRef.current = name;
|
|
4041
4428
|
setDescOpen(false);
|
|
4042
4429
|
setDescClamped(false);
|
|
4043
4430
|
}, [name]);
|
|
4044
|
-
|
|
4431
|
+
import_react9.default.useLayoutEffect(() => {
|
|
4045
4432
|
const node = descRef.current;
|
|
4046
4433
|
if (!node) return void 0;
|
|
4047
4434
|
const measure = () => {
|
|
@@ -4063,16 +4450,16 @@ function SkillReader({
|
|
|
4063
4450
|
window.removeEventListener("resize", measure);
|
|
4064
4451
|
};
|
|
4065
4452
|
}, [description, descOpen]);
|
|
4066
|
-
|
|
4453
|
+
import_react9.default.useEffect(() => {
|
|
4067
4454
|
if (!initialFile) return;
|
|
4068
4455
|
setTab("files");
|
|
4069
4456
|
void loadFile(initialFile);
|
|
4070
4457
|
}, [initialFile, loadFile]);
|
|
4071
|
-
|
|
4458
|
+
import_react9.default.useEffect(() => {
|
|
4072
4459
|
const node = bodyRef.current;
|
|
4073
4460
|
if (node && scrollRef.current) node.scrollTop = scrollRef.current;
|
|
4074
4461
|
});
|
|
4075
|
-
|
|
4462
|
+
import_react9.default.useEffect(() => {
|
|
4076
4463
|
if (variant !== "drawer" || typeof document === "undefined") return void 0;
|
|
4077
4464
|
const previous = document.activeElement;
|
|
4078
4465
|
const node = dialogRef.current;
|
|
@@ -4099,12 +4486,12 @@ function SkillReader({
|
|
|
4099
4486
|
previous?.focus?.();
|
|
4100
4487
|
};
|
|
4101
4488
|
}, [variant, onClose]);
|
|
4102
|
-
|
|
4489
|
+
import_react9.default.useEffect(() => {
|
|
4103
4490
|
if (variant !== "page" || typeof document === "undefined") return;
|
|
4104
4491
|
const node = document.getElementById("dshsc-reader-name");
|
|
4105
4492
|
node?.focus?.();
|
|
4106
4493
|
}, [variant, name]);
|
|
4107
|
-
|
|
4494
|
+
import_react9.default.useEffect(() => {
|
|
4108
4495
|
if (variant !== "page" || typeof window === "undefined") return void 0;
|
|
4109
4496
|
const node = bodyRef.current;
|
|
4110
4497
|
if (!node?.querySelectorAll) return void 0;
|
|
@@ -4200,7 +4587,7 @@ function SkillReader({
|
|
|
4200
4587
|
h("span", { className: "dshsc-code" }, row?.source ? `${row.source}${row.commit ? `#${short(row.commit)}` : row.ref ? `#${row.ref}` : ""}` : t("meta.noUpstream"))
|
|
4201
4588
|
),
|
|
4202
4589
|
tree ? h("span", null, t("viewer.fileCount").replace("{n}", String(fileCount))) : null,
|
|
4203
|
-
row?.updatedAt ? h("span", null,
|
|
4590
|
+
row?.updatedAt ? h("span", null, formatWhen2(row.updatedAt)) : null
|
|
4204
4591
|
)
|
|
4205
4592
|
);
|
|
4206
4593
|
const tabsBar = h(
|
|
@@ -4417,8 +4804,8 @@ function SkillReader({
|
|
|
4417
4804
|
["viewer.info.hash", hash ? h("span", { className: "mono" }, hash) : t("viewer.info.none"), "mono"]
|
|
4418
4805
|
];
|
|
4419
4806
|
const localRows = [
|
|
4420
|
-
["viewer.info.installedAt",
|
|
4421
|
-
["viewer.info.updatedAt",
|
|
4807
|
+
["viewer.info.installedAt", formatWhen2(row?.installedAt) || t("viewer.info.none")],
|
|
4808
|
+
["viewer.info.updatedAt", formatWhen2(row?.updatedAt) || t("viewer.info.none")],
|
|
4422
4809
|
["viewer.info.size", tree ? `${formatBytes(tree.totalBytes)} · ${t("viewer.fileCount").replace("{n}", String(fileCount))}` : t("viewer.info.none")],
|
|
4423
4810
|
["viewer.info.localChange", row?.status === "locally-modified" || row?.status === "conflict" ? t("viewer.info.localChangeYes") : t("viewer.info.localChangeNone")]
|
|
4424
4811
|
];
|
|
@@ -4542,19 +4929,19 @@ function SkillReader({
|
|
|
4542
4929
|
var UPDATABLE2 = /* @__PURE__ */ new Set(["update-available", "locally-modified", "conflict"]);
|
|
4543
4930
|
function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
4544
4931
|
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] =
|
|
4932
|
+
const [tab, setTab] = import_react10.default.useState("installed");
|
|
4933
|
+
const [view, setView] = import_react10.default.useState("flat");
|
|
4934
|
+
const [modal, setModal] = import_react10.default.useState(null);
|
|
4935
|
+
const [expanded, setExpanded] = import_react10.default.useState({});
|
|
4936
|
+
const [placementBusy, setPlacementBusy] = import_react10.default.useState(null);
|
|
4937
|
+
const [placementOverride, setPlacementOverride] = import_react10.default.useState(null);
|
|
4938
|
+
const [query, setQuery] = import_react10.default.useState("");
|
|
4939
|
+
const [viewer, setViewer] = import_react10.default.useState(null);
|
|
4940
|
+
const rootRef = import_react10.default.useRef(null);
|
|
4941
|
+
const scrollPos = import_react10.default.useRef(null);
|
|
4942
|
+
const restoreScroll = import_react10.default.useRef(null);
|
|
4943
|
+
const [filter, setFilter] = import_react10.default.useState("all");
|
|
4944
|
+
const [builtinOpen, setBuiltinOpen] = import_react10.default.useState(false);
|
|
4558
4945
|
const { settingsOpen, settingsRef, settingsPlacement, closeSettings, toggleSettings } = useSettingsPopover();
|
|
4559
4946
|
const { agentTip, agentTipPos, agentTipRef, openAgentTip, closeAgentTip } = useAgentTip();
|
|
4560
4947
|
const rows = data?.installs || [];
|
|
@@ -4577,7 +4964,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
4577
4964
|
const agentLabels = row.exports.map((item) => item.label);
|
|
4578
4965
|
return [row.name, row.source || "", ...agentLabels].join(" ").toLowerCase().includes(queryText);
|
|
4579
4966
|
});
|
|
4580
|
-
|
|
4967
|
+
import_react10.default.useEffect(() => {
|
|
4581
4968
|
if (!viewer || !data) return;
|
|
4582
4969
|
if (rows.some((row) => row.name === viewer.name)) return;
|
|
4583
4970
|
setViewer(null);
|
|
@@ -4611,7 +4998,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
4611
4998
|
if (viewer?.mode === "page") restoreScroll.current = scrollPos.current;
|
|
4612
4999
|
setViewer(null);
|
|
4613
5000
|
}
|
|
4614
|
-
|
|
5001
|
+
import_react10.default.useLayoutEffect(() => {
|
|
4615
5002
|
if (restoreScroll.current === null) return;
|
|
4616
5003
|
const target = restoreScroll.current;
|
|
4617
5004
|
restoreScroll.current = null;
|
|
@@ -4680,7 +5067,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
4680
5067
|
onToast: (next) => setToast(next)
|
|
4681
5068
|
}) : null;
|
|
4682
5069
|
const listNode = h(
|
|
4683
|
-
|
|
5070
|
+
import_react10.default.Fragment,
|
|
4684
5071
|
null,
|
|
4685
5072
|
h(Header, {
|
|
4686
5073
|
t,
|
|
@@ -4738,6 +5125,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
4738
5125
|
expanded,
|
|
4739
5126
|
busy,
|
|
4740
5127
|
writable,
|
|
5128
|
+
capability: data?.intro || null,
|
|
4741
5129
|
...subscriptionActions
|
|
4742
5130
|
}),
|
|
4743
5131
|
busy ? h("div", { className: "dshsc-meta" }, `${t("busy.working")} ${busy}`) : null
|