@morehao/dsh-skills-console 0.5.4 → 0.5.6
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.en.md +18 -6
- package/README.md +16 -4
- package/client.js +414 -81
- package/lib/core/hash.js +25 -4
- package/lib/core/intro-store.js +5 -2
- package/lib/core/intro.js +112 -18
- package/lib/core/invocation.js +31 -5
- package/lib/core/plugin-update.js +323 -0
- package/lib/core/service.js +82 -1
- package/lib/index.js +29 -1
- package/lib/routes.js +26 -3
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -69,6 +69,15 @@ async function apiCall(method, path, body, options = {}) {
|
|
|
69
69
|
return json;
|
|
70
70
|
}
|
|
71
71
|
var GITHUB_URL = "https://github.com/morehao/dsh-skills-console";
|
|
72
|
+
function checkPluginUpdate({ fresh = false } = {}) {
|
|
73
|
+
return apiCall("GET", `/plugin-update${fresh ? "?fresh=1" : ""}`);
|
|
74
|
+
}
|
|
75
|
+
function applyPluginUpdate() {
|
|
76
|
+
return apiCall("POST", "/plugin-update/apply", {});
|
|
77
|
+
}
|
|
78
|
+
function fetchPluginRelease(version) {
|
|
79
|
+
return apiCall("GET", `/plugin-update/release?version=${encodeURIComponent(version || "")}`);
|
|
80
|
+
}
|
|
72
81
|
|
|
73
82
|
// client/copy.js
|
|
74
83
|
var DICT = {
|
|
@@ -117,10 +126,33 @@ var DICT = {
|
|
|
117
126
|
"placement.off": "已关闭",
|
|
118
127
|
"placement.hint": "开启后侧栏出现「技能控制台」入口;关闭则只在「设置 → 技能」显示。",
|
|
119
128
|
"github.open": "打开 GitHub 仓库",
|
|
129
|
+
"pluginUpdate.check": "检查插件更新",
|
|
130
|
+
"pluginUpdate.title": "检查插件更新",
|
|
131
|
+
"pluginUpdate.current": "当前版本",
|
|
132
|
+
"pluginUpdate.latest": "最新版本",
|
|
133
|
+
"pluginUpdate.checking": "正在检查…",
|
|
134
|
+
"pluginUpdate.upToDate": "已是最新版本",
|
|
135
|
+
"pluginUpdate.available": "有新版本可用",
|
|
136
|
+
"pluginUpdate.checkFailed": "暂时无法获取最新版本,请稍后再试",
|
|
137
|
+
"pluginUpdate.recheck": "重新检查",
|
|
138
|
+
"pluginUpdate.update": "更新到 {version}",
|
|
139
|
+
"pluginUpdate.applying": "正在更新…",
|
|
140
|
+
"pluginUpdate.updated": "已更新到 {version},重启 DSH 后生效",
|
|
141
|
+
"pluginUpdate.restartHint": "更新已安装,请重启 DSH 使新版本生效。",
|
|
142
|
+
"pluginUpdate.manual": "当前环境不支持一键更新,请手动执行:",
|
|
143
|
+
"pluginUpdate.copy": "复制命令",
|
|
144
|
+
"pluginUpdate.copied": "已复制",
|
|
145
|
+
"pluginUpdate.close": "关闭",
|
|
146
|
+
"pluginUpdate.whatsNew": "v{version} 更新内容",
|
|
147
|
+
"pluginUpdate.releaseLoading": "正在加载更新内容…",
|
|
148
|
+
"pluginUpdate.viewReleaseNotes": "在 GitHub 查看发布说明",
|
|
149
|
+
"pluginUpdate.releaseUnavailable": "暂无该版本的发布说明。",
|
|
120
150
|
"status.current": "最新",
|
|
121
151
|
"status.update": "可更新",
|
|
122
|
-
"status.local": "
|
|
123
|
-
"status.
|
|
152
|
+
"status.local": "安装后有改动",
|
|
153
|
+
"status.localHint": "安装或更新后,这个技能的文件被改过;再次更新会覆盖这些改动,会先请你确认。可在「查看 → 信息」里核对",
|
|
154
|
+
"status.conflict": "有改动 · 可更新",
|
|
155
|
+
"status.conflictHint": "本地文件有改动,上游也有新提交;更新会覆盖你的改动(旧内容有备份,可回滚)",
|
|
124
156
|
"status.deleted": "上游已移除",
|
|
125
157
|
"status.broken": "异常:软链断裂",
|
|
126
158
|
"status.unmanaged": "未纳入追踪",
|
|
@@ -212,6 +244,7 @@ var DICT = {
|
|
|
212
244
|
"form.subpath": "子目录(可空)",
|
|
213
245
|
"form.hint": "会立即下载并写入 ~/.agents/skills,并按「新安装时自动共享」的选择共享至对应 Agent。",
|
|
214
246
|
"form.subHint": "只记录你关注这个仓库,不会安装任何技能。",
|
|
247
|
+
"form.submitFailed": "添加失败,请重试",
|
|
215
248
|
"subscription.defaultRef": "默认分支",
|
|
216
249
|
"confirm.deleteTitle": "删除技能",
|
|
217
250
|
"confirm.deleteBody": "会从本机移除,断开本插件建立的其他 Agent 链接并清理记录;目录先备份到回收站(保留 7 天)。不会删除你的源仓库。",
|
|
@@ -295,9 +328,9 @@ var DICT = {
|
|
|
295
328
|
"viewer.info.updatedAt": "更新",
|
|
296
329
|
"viewer.info.size": "大小",
|
|
297
330
|
"viewer.info.local": "本地",
|
|
298
|
-
"viewer.info.localChange": "
|
|
331
|
+
"viewer.info.localChange": "安装后改动",
|
|
299
332
|
"viewer.info.localChangeNone": "无(与安装时一致)",
|
|
300
|
-
"viewer.info.localChangeYes": "
|
|
333
|
+
"viewer.info.localChangeYes": "有改动",
|
|
301
334
|
"viewer.info.links": "共享",
|
|
302
335
|
"viewer.info.exported": "已共享",
|
|
303
336
|
"viewer.info.visibility": "可见性",
|
|
@@ -324,6 +357,9 @@ var DICT = {
|
|
|
324
357
|
"intro.error.timeout": "生成超时,请重试",
|
|
325
358
|
"intro.error.auth": "模型提供方认证失败",
|
|
326
359
|
"intro.error.empty": "模型没有返回内容",
|
|
360
|
+
"intro.error.truncated": "简介被输出上限截断,请重试",
|
|
361
|
+
"intro.error.reasoningOnly": "模型只输出了思考过程,没有输出正文;请提高输出上限或换非推理模型",
|
|
362
|
+
"intro.error.noText": "模型没有输出文字简介,请重试",
|
|
327
363
|
"intro.error.busy": "正在生成,请稍候",
|
|
328
364
|
"intro.error.source": "读不到远端的 SKILL.md(仓库私有、已删除或网络问题)",
|
|
329
365
|
"intro.error.noSource": "订阅快照里没有这个技能,请先点「检查更新」",
|
|
@@ -376,10 +412,33 @@ var DICT = {
|
|
|
376
412
|
"placement.off": "off",
|
|
377
413
|
"placement.hint": "On: a Skills Console entry appears in the sidebar. Off: it only shows under Settings → Skills.",
|
|
378
414
|
"github.open": "Open GitHub repository",
|
|
415
|
+
"pluginUpdate.check": "Check for plugin updates",
|
|
416
|
+
"pluginUpdate.title": "Check for plugin updates",
|
|
417
|
+
"pluginUpdate.current": "Current version",
|
|
418
|
+
"pluginUpdate.latest": "Latest version",
|
|
419
|
+
"pluginUpdate.checking": "Checking…",
|
|
420
|
+
"pluginUpdate.upToDate": "You are on the latest version",
|
|
421
|
+
"pluginUpdate.available": "A new version is available",
|
|
422
|
+
"pluginUpdate.checkFailed": "Could not reach the registry; please try again later",
|
|
423
|
+
"pluginUpdate.recheck": "Check again",
|
|
424
|
+
"pluginUpdate.update": "Update to {version}",
|
|
425
|
+
"pluginUpdate.applying": "Updating…",
|
|
426
|
+
"pluginUpdate.updated": "Updated to {version}; restart DSH to apply",
|
|
427
|
+
"pluginUpdate.restartHint": "The update is installed. Restart DSH to use the new version.",
|
|
428
|
+
"pluginUpdate.manual": "One-click update is unavailable here; run this manually:",
|
|
429
|
+
"pluginUpdate.copy": "Copy command",
|
|
430
|
+
"pluginUpdate.copied": "Copied",
|
|
431
|
+
"pluginUpdate.close": "Close",
|
|
432
|
+
"pluginUpdate.whatsNew": "What's new in v{version}",
|
|
433
|
+
"pluginUpdate.releaseLoading": "Loading release notes…",
|
|
434
|
+
"pluginUpdate.viewReleaseNotes": "View release notes on GitHub",
|
|
435
|
+
"pluginUpdate.releaseUnavailable": "No release notes for this version.",
|
|
379
436
|
"status.current": "Up to date",
|
|
380
437
|
"status.update": "Update available",
|
|
381
|
-
"status.local": "
|
|
382
|
-
"status.
|
|
438
|
+
"status.local": "Changed since install",
|
|
439
|
+
"status.localHint": "Files were changed after install or update. Updating overwrites them and asks for confirmation first. Check “View → Info” to compare",
|
|
440
|
+
"status.conflict": "Changed · update available",
|
|
441
|
+
"status.conflictHint": "Local files changed and upstream has new commits; updating overwrites your edits (old content is backed up and can be rolled back)",
|
|
383
442
|
"status.deleted": "Removed upstream",
|
|
384
443
|
"status.broken": "Broken link",
|
|
385
444
|
"status.unmanaged": "Untracked",
|
|
@@ -470,6 +529,7 @@ var DICT = {
|
|
|
470
529
|
"form.subpath": "Subdirectory (optional)",
|
|
471
530
|
"form.hint": "Downloads into ~/.agents/skills and shares it with agents per the auto-share setting.",
|
|
472
531
|
"form.subHint": "Only records the repository; installs nothing.",
|
|
532
|
+
"form.submitFailed": "Failed; please try again",
|
|
473
533
|
"subscription.defaultRef": "default branch",
|
|
474
534
|
"confirm.deleteTitle": "Delete skill",
|
|
475
535
|
"confirm.deleteBody": "Removes it locally, disconnects the agent links this plugin created, and clears the record. A snapshot stays in trash for 7 days. Your source repo is untouched.",
|
|
@@ -554,9 +614,9 @@ var DICT = {
|
|
|
554
614
|
"viewer.info.updatedAt": "Updated",
|
|
555
615
|
"viewer.info.size": "Size",
|
|
556
616
|
"viewer.info.local": "Local",
|
|
557
|
-
"viewer.info.localChange": "
|
|
617
|
+
"viewer.info.localChange": "Changes since install",
|
|
558
618
|
"viewer.info.localChangeNone": "None (matches install)",
|
|
559
|
-
"viewer.info.localChangeYes": "
|
|
619
|
+
"viewer.info.localChangeYes": "Changed",
|
|
560
620
|
"viewer.info.links": "Shared",
|
|
561
621
|
"viewer.info.exported": "Shared",
|
|
562
622
|
"viewer.info.visibility": "Visibility",
|
|
@@ -583,6 +643,9 @@ var DICT = {
|
|
|
583
643
|
"intro.error.timeout": "Generation timed out; try again",
|
|
584
644
|
"intro.error.auth": "The model provider rejected the credentials",
|
|
585
645
|
"intro.error.empty": "The model returned no content",
|
|
646
|
+
"intro.error.truncated": "The intro hit the output limit and was truncated; try again",
|
|
647
|
+
"intro.error.reasoningOnly": "The model only produced reasoning, not intro text; raise the output limit or use a non-reasoning model",
|
|
648
|
+
"intro.error.noText": "The model returned no text intro; try again",
|
|
586
649
|
"intro.error.busy": "Already generating; please wait",
|
|
587
650
|
"intro.error.source": "Could not read the remote SKILL.md (private repo, deleted, or network)",
|
|
588
651
|
"intro.error.noSource": "This skill is not in the subscription snapshot; run “Check for updates” first",
|
|
@@ -1040,7 +1103,7 @@ function FolderIcon({ size = 12 }) {
|
|
|
1040
1103
|
}
|
|
1041
1104
|
|
|
1042
1105
|
// client/section.js
|
|
1043
|
-
var
|
|
1106
|
+
var import_react10 = __toESM(require("react"), 1);
|
|
1044
1107
|
|
|
1045
1108
|
// client/scroll.js
|
|
1046
1109
|
function isScroller(node, tolerance = 4) {
|
|
@@ -1097,6 +1160,7 @@ var CSS = `
|
|
|
1097
1160
|
/* 标题不参与收窄:窄面板里让身份胶囊先省略,标题本身不折行 */
|
|
1098
1161
|
.dshsc-title { font-size: 17px; font-weight: 600; letter-spacing: -.01em; margin: 0; flex: none; white-space: nowrap; }
|
|
1099
1162
|
.dshsc-sub { font-size: 12px; line-height: 1.55; color: var(--dsw-alias-label-secondary); margin: 0; max-width: 64ch; }
|
|
1163
|
+
.dshsc-form-error { font-size: 12px; line-height: 1.55; margin: 8px 0 0; max-width: 64ch; color: var(--dsw-alias-state-error-primary); }
|
|
1100
1164
|
.dshsc-spacer { flex: 1; }
|
|
1101
1165
|
.dshsc-head-actions { display: flex; align-items: center; justify-content: flex-end; gap: 6px; flex-wrap: wrap; margin-left: auto; }
|
|
1102
1166
|
/* 标题行恒为一行:身份胶囊只在面板极窄(连标题行都放不下)时才省略,工具组不被挤走。
|
|
@@ -1121,6 +1185,10 @@ var CSS = `
|
|
|
1121
1185
|
.dshsc-tool:hover { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); }
|
|
1122
1186
|
.dshsc-tool:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }
|
|
1123
1187
|
.dshsc-tool[aria-expanded="true"] { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); }
|
|
1188
|
+
/* 刷新按钮:检查到新版本时右上角点个品牌色圆点 */
|
|
1189
|
+
.dshsc-tool-dot { position: relative; color: var(--dsw-alias-brand-primary); }
|
|
1190
|
+
.dshsc-tool-dot::after { content: ""; position: absolute; top: 3px; right: 3px; width: 6px; height: 6px;
|
|
1191
|
+
border-radius: 50%; background: var(--dsw-alias-state-warning-primary, #e8b15a); box-shadow: 0 0 0 1.5px var(--dsw-alias-bg-base); }
|
|
1124
1192
|
.dshsc-settings { position: relative; display: inline-flex; }
|
|
1125
1193
|
/* 设置面板留在 Host 对话框内(焦点陷阱 / 层级都跟着走);开启时按左右余量选边和宽度 */
|
|
1126
1194
|
.dshsc-pop { position: absolute; top: calc(100% + 8px); z-index: 30; box-sizing: border-box;
|
|
@@ -1483,6 +1551,10 @@ var CSS = `
|
|
|
1483
1551
|
/* 64ch 是主页面长文的舒适行长;弹窗说明只有一两句,用满弹窗宽度,避免没必要的折行 */
|
|
1484
1552
|
.dshsc-modal .dshsc-sub { max-width: none; }
|
|
1485
1553
|
.dshsc-modal h3 { margin: 0; font-size: 15px; font-weight: 600; padding-right: 26px; }
|
|
1554
|
+
/* 插件更新弹窗的「当前 / 最新」键值对 */
|
|
1555
|
+
.dshsc-kv { display: grid; grid-template-columns: auto 1fr; gap: 6px 12px; margin: 0; font-size: 12px; align-items: baseline; }
|
|
1556
|
+
.dshsc-kv dt { color: var(--dsw-alias-label-secondary); }
|
|
1557
|
+
.dshsc-kv dd { margin: 0; }
|
|
1486
1558
|
.dshsc-modal-close { position: absolute; top: 10px; right: 10px; width: 26px; height: 26px; border: 0; border-radius: 7px;
|
|
1487
1559
|
background: transparent; color: var(--dsw-alias-label-secondary); font-size: 18px; line-height: 1; cursor: pointer; }
|
|
1488
1560
|
.dshsc-modal-close:hover { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); }
|
|
@@ -1532,6 +1604,14 @@ var CSS = `
|
|
|
1532
1604
|
.dshsc-panel .dshsc-check.locked:hover { background: transparent; }
|
|
1533
1605
|
|
|
1534
1606
|
.dshsc-modal-foot { display: flex; justify-content: flex-end; gap: 8px; padding-top: 12px; border-top: 1px solid var(--dsw-alias-border-l1); }
|
|
1607
|
+
/* 弹窗页脚按钮可能是「图标 + 文字」:统一成水平排列,避免 svg(display:block) 把文字挤到下一行 */
|
|
1608
|
+
.dshsc-modal-foot .dshsc-btn { display: inline-flex; align-items: center; justify-content: center; gap: 5px; }
|
|
1609
|
+
.dshsc-modal-foot .dshsc-btn svg { display: block; }
|
|
1610
|
+
/* 插件更新弹窗里的「更新内容」(GitHub Release 正文):限高滚动,长说明不撑爆弹窗 */
|
|
1611
|
+
.dshsc-release-head { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary); margin-bottom: 6px; }
|
|
1612
|
+
.dshsc-release-body { max-height: 260px; overflow: auto; padding: 10px 12px; border-radius: 10px;
|
|
1613
|
+
background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); }
|
|
1614
|
+
.dshsc-release-body .dshsc-md { font-size: 12.5px; }
|
|
1535
1615
|
/* ---------- 查看器:抽屉(B1)/ 整页(A1)共用 ---------- */
|
|
1536
1616
|
.dshsc-drawer-wrap { position: fixed; inset: 0; z-index: 55; }
|
|
1537
1617
|
.dshsc-drawer-scrim { position: absolute; inset: 0; background: rgba(4,6,9,.55); }
|
|
@@ -1913,26 +1993,38 @@ function useSkillActions({ t, rows, reload, run, setBusy, setToast, setModal })
|
|
|
1913
1993
|
return apiCall("POST", "/probe", { source: String(source || "").trim() });
|
|
1914
1994
|
}
|
|
1915
1995
|
async function installSkills({ source, skills }) {
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1996
|
+
try {
|
|
1997
|
+
await run("install", async () => {
|
|
1998
|
+
const result = await apiCall("POST", "/install", { source: String(source || "").trim(), skills });
|
|
1999
|
+
const warnings = result.warnings?.length || 0;
|
|
2000
|
+
setToast({
|
|
2001
|
+
kind: warnings ? "err" : "ok",
|
|
2002
|
+
text: `${result.installed?.length || 0} ${t("skillCount")}${warnings ? ` · ${warnings} 条提示` : ""}`
|
|
2003
|
+
});
|
|
2004
|
+
return result;
|
|
2005
|
+
}, { toastError: false });
|
|
2006
|
+
setModal(null);
|
|
2007
|
+
return { ok: true };
|
|
2008
|
+
} catch (err) {
|
|
2009
|
+
return { ok: false, error: err?.message || String(err) };
|
|
2010
|
+
}
|
|
1925
2011
|
}
|
|
1926
2012
|
async function addSubscription(form) {
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
2013
|
+
try {
|
|
2014
|
+
const result = await run("subscribe", async () => {
|
|
2015
|
+
const res = await apiCall("POST", "/subscriptions", {
|
|
2016
|
+
source: form.source.trim(),
|
|
2017
|
+
ref: form.ref.trim() || void 0,
|
|
2018
|
+
subpath: form.subpath.trim() || void 0
|
|
2019
|
+
});
|
|
2020
|
+
setToast({ kind: "ok", text: res.subscription?.source || form.source });
|
|
2021
|
+
return res;
|
|
2022
|
+
}, { toastError: false });
|
|
2023
|
+
setModal(null);
|
|
2024
|
+
return { ok: true, result };
|
|
2025
|
+
} catch (err) {
|
|
2026
|
+
return { ok: false, error: err?.message || String(err) };
|
|
2027
|
+
}
|
|
1936
2028
|
}
|
|
1937
2029
|
async function refreshSubscription(row) {
|
|
1938
2030
|
await run(`refresh:${row.id}`, async () => {
|
|
@@ -1999,14 +2091,14 @@ function useSkillsData() {
|
|
|
1999
2091
|
return () => clearTimeout(timer);
|
|
2000
2092
|
}, [toast]);
|
|
2001
2093
|
const run = import_react2.default.useCallback(
|
|
2002
|
-
async (label, task) => {
|
|
2094
|
+
async (label, task, { toastError = true } = {}) => {
|
|
2003
2095
|
setBusy(label);
|
|
2004
2096
|
try {
|
|
2005
2097
|
const result = await task();
|
|
2006
2098
|
await reload();
|
|
2007
2099
|
return result;
|
|
2008
2100
|
} catch (err) {
|
|
2009
|
-
setToast({ kind: "err", text: err.message });
|
|
2101
|
+
if (toastError) setToast({ kind: "err", text: err.message });
|
|
2010
2102
|
throw err;
|
|
2011
2103
|
} finally {
|
|
2012
2104
|
setBusy(null);
|
|
@@ -2254,6 +2346,64 @@ function useSkillIntro({ name, lang, stamp, enabled = true, endpoint = null }) {
|
|
|
2254
2346
|
}, [name, lang, base]);
|
|
2255
2347
|
return { ...state, generating, genError, generate };
|
|
2256
2348
|
}
|
|
2349
|
+
function usePluginUpdate() {
|
|
2350
|
+
const [status, setStatus] = import_react2.default.useState(null);
|
|
2351
|
+
const [checking, setChecking] = import_react2.default.useState(false);
|
|
2352
|
+
const [applying, setApplying] = import_react2.default.useState(false);
|
|
2353
|
+
const [checkError, setCheckError] = import_react2.default.useState(null);
|
|
2354
|
+
const [applyError, setApplyError] = import_react2.default.useState(null);
|
|
2355
|
+
const [release, setRelease] = import_react2.default.useState({ loading: false, data: null, version: null });
|
|
2356
|
+
const inFlight = import_react2.default.useRef(false);
|
|
2357
|
+
const check = import_react2.default.useCallback(async (fresh = false) => {
|
|
2358
|
+
if (inFlight.current) return null;
|
|
2359
|
+
inFlight.current = true;
|
|
2360
|
+
setChecking(true);
|
|
2361
|
+
setCheckError(null);
|
|
2362
|
+
try {
|
|
2363
|
+
const next = await checkPluginUpdate({ fresh });
|
|
2364
|
+
setStatus(next);
|
|
2365
|
+
return next;
|
|
2366
|
+
} catch (error) {
|
|
2367
|
+
setCheckError({ code: error?.code || null, message: error?.message || "检查失败" });
|
|
2368
|
+
return null;
|
|
2369
|
+
} finally {
|
|
2370
|
+
inFlight.current = false;
|
|
2371
|
+
setChecking(false);
|
|
2372
|
+
}
|
|
2373
|
+
}, []);
|
|
2374
|
+
const apply2 = import_react2.default.useCallback(async () => {
|
|
2375
|
+
setApplying(true);
|
|
2376
|
+
setApplyError(null);
|
|
2377
|
+
try {
|
|
2378
|
+
const next = await applyPluginUpdate();
|
|
2379
|
+
setStatus(next);
|
|
2380
|
+
return next;
|
|
2381
|
+
} catch (error) {
|
|
2382
|
+
setApplyError({
|
|
2383
|
+
code: error?.code || null,
|
|
2384
|
+
message: error?.message || "更新失败",
|
|
2385
|
+
detail: error?.detail || null
|
|
2386
|
+
});
|
|
2387
|
+
return null;
|
|
2388
|
+
} finally {
|
|
2389
|
+
setApplying(false);
|
|
2390
|
+
}
|
|
2391
|
+
}, []);
|
|
2392
|
+
const loadRelease = import_react2.default.useCallback(async (version) => {
|
|
2393
|
+
const target = String(version || "").trim();
|
|
2394
|
+
if (!target) return null;
|
|
2395
|
+
setRelease({ loading: true, data: null, version: target });
|
|
2396
|
+
try {
|
|
2397
|
+
const data = await fetchPluginRelease(target);
|
|
2398
|
+
setRelease({ loading: false, data: data || null, version: target });
|
|
2399
|
+
return data;
|
|
2400
|
+
} catch {
|
|
2401
|
+
setRelease({ loading: false, data: null, version: target });
|
|
2402
|
+
return null;
|
|
2403
|
+
}
|
|
2404
|
+
}, []);
|
|
2405
|
+
return { status, checking, applying, checkError, applyError, check, apply: apply2, release, loadRelease };
|
|
2406
|
+
}
|
|
2257
2407
|
|
|
2258
2408
|
// client/views/header.js
|
|
2259
2409
|
function installSpec(plugin) {
|
|
@@ -2275,6 +2425,8 @@ function Header({
|
|
|
2275
2425
|
placement,
|
|
2276
2426
|
placementBusy,
|
|
2277
2427
|
readOnly,
|
|
2428
|
+
pluginUpdate,
|
|
2429
|
+
onCheckUpdate,
|
|
2278
2430
|
onToggleSettings,
|
|
2279
2431
|
onChangePlacement,
|
|
2280
2432
|
onOpenAutoLink,
|
|
@@ -2282,9 +2434,18 @@ function Header({
|
|
|
2282
2434
|
onAddSubscription
|
|
2283
2435
|
}) {
|
|
2284
2436
|
const spec = installSpec(plugin);
|
|
2437
|
+
const updateAvailable = Boolean(pluginUpdate?.status?.updateAvailable);
|
|
2285
2438
|
const headTools = h(
|
|
2286
2439
|
"div",
|
|
2287
2440
|
{ className: "dshsc-tools" },
|
|
2441
|
+
// 刷新按钮放在 GitHub 前面:点一下检查插件自身版本,有新版本时高亮提示
|
|
2442
|
+
h("button", {
|
|
2443
|
+
className: `dshsc-tool${updateAvailable ? " dshsc-tool-dot" : ""}`,
|
|
2444
|
+
type: "button",
|
|
2445
|
+
title: t("pluginUpdate.check"),
|
|
2446
|
+
"aria-label": t("pluginUpdate.check"),
|
|
2447
|
+
onClick: onCheckUpdate
|
|
2448
|
+
}, h(RefreshIcon, { size: 15 })),
|
|
2288
2449
|
// 先源码、后设置:GitHub 紧挨包名,齿轮留在工具组最外侧
|
|
2289
2450
|
h("a", {
|
|
2290
2451
|
className: "dshsc-tool",
|
|
@@ -2450,12 +2611,13 @@ function Toolbar({
|
|
|
2450
2611
|
var import_react4 = __toESM(require("react"), 1);
|
|
2451
2612
|
|
|
2452
2613
|
// client/views/overlay.js
|
|
2453
|
-
function Overlay({ t, title, children, footer, onCancel }) {
|
|
2454
|
-
useOverlayLayer(true, onCancel)
|
|
2614
|
+
function Overlay({ t, title, children, footer, onCancel, dismissible = true }) {
|
|
2615
|
+
useOverlayLayer(true, dismissible ? onCancel : () => {
|
|
2616
|
+
});
|
|
2455
2617
|
return portalize(h(
|
|
2456
2618
|
"div",
|
|
2457
2619
|
{ className: "dshsc-overlay", onClick: (event) => {
|
|
2458
|
-
if (event.target === event.currentTarget) onCancel?.();
|
|
2620
|
+
if (dismissible && event.target === event.currentTarget) onCancel?.();
|
|
2459
2621
|
} },
|
|
2460
2622
|
h(
|
|
2461
2623
|
"div",
|
|
@@ -2463,9 +2625,10 @@ function Overlay({ t, title, children, footer, onCancel }) {
|
|
|
2463
2625
|
h("button", {
|
|
2464
2626
|
className: "dshsc-modal-close",
|
|
2465
2627
|
"aria-label": t("action.cancel"),
|
|
2628
|
+
disabled: !dismissible,
|
|
2466
2629
|
onClick: (event) => {
|
|
2467
2630
|
event.stopPropagation();
|
|
2468
|
-
onCancel?.();
|
|
2631
|
+
if (dismissible) onCancel?.();
|
|
2469
2632
|
}
|
|
2470
2633
|
}, "×"),
|
|
2471
2634
|
h("h3", null, title),
|
|
@@ -2484,6 +2647,9 @@ var ERROR_KEY = {
|
|
|
2484
2647
|
"error.skills.intro-timeout": "intro.error.timeout",
|
|
2485
2648
|
"error.skills.intro-auth": "intro.error.auth",
|
|
2486
2649
|
"error.skills.intro-empty": "intro.error.empty",
|
|
2650
|
+
"error.skills.intro-truncated": "intro.error.truncated",
|
|
2651
|
+
"error.skills.intro-reasoning-only": "intro.error.reasoningOnly",
|
|
2652
|
+
"error.skills.intro-no-text": "intro.error.noText",
|
|
2487
2653
|
"error.skills.intro-busy": "intro.error.busy",
|
|
2488
2654
|
"error.skills.intro-source": "intro.error.source",
|
|
2489
2655
|
"error.skills.subscription-not-found": "intro.error.subscription",
|
|
@@ -2805,14 +2971,17 @@ function InstalledTab({
|
|
|
2805
2971
|
const map = {
|
|
2806
2972
|
current: ["status.current", "ok", true],
|
|
2807
2973
|
"update-available": ["status.update", "warn"],
|
|
2808
|
-
"locally-modified": ["status.local", "warn"],
|
|
2809
|
-
conflict: ["status.conflict", "warn"],
|
|
2974
|
+
"locally-modified": ["status.local", "warn", false, "status.localHint"],
|
|
2975
|
+
conflict: ["status.conflict", "warn", false, "status.conflictHint"],
|
|
2810
2976
|
"upstream-deleted": ["status.deleted", "warn"],
|
|
2811
2977
|
"broken-link": ["status.broken", "err"],
|
|
2812
2978
|
unmanaged: ["status.unmanaged", ""]
|
|
2813
2979
|
};
|
|
2814
|
-
const [key, cls, check] = map[row.status] || ["status.unknown", ""];
|
|
2815
|
-
return h("span", {
|
|
2980
|
+
const [key, cls, check, hint] = map[row.status] || ["status.unknown", ""];
|
|
2981
|
+
return h("span", {
|
|
2982
|
+
className: `dshsc-tag ${cls}`,
|
|
2983
|
+
title: hint ? t(hint) : void 0
|
|
2984
|
+
}, check ? `✓ ${t(key)}` : t(key));
|
|
2816
2985
|
}
|
|
2817
2986
|
function sourceLine(row) {
|
|
2818
2987
|
if (!row.source) {
|
|
@@ -3471,6 +3640,8 @@ function AddSkillModal({ t, onCancel, onProbe, onInstall }) {
|
|
|
3471
3640
|
const [manual, setManual] = import_react6.default.useState("");
|
|
3472
3641
|
const [skipped, setSkipped] = import_react6.default.useState(0);
|
|
3473
3642
|
const [error, setError] = import_react6.default.useState(null);
|
|
3643
|
+
const [pending, setPending] = import_react6.default.useState(false);
|
|
3644
|
+
const [submitError, setSubmitError] = import_react6.default.useState(null);
|
|
3474
3645
|
const reset = (value) => {
|
|
3475
3646
|
setSource(value);
|
|
3476
3647
|
setPhase("idle");
|
|
@@ -3478,12 +3649,14 @@ function AddSkillModal({ t, onCancel, onProbe, onInstall }) {
|
|
|
3478
3649
|
setSelected(/* @__PURE__ */ new Set());
|
|
3479
3650
|
setSkipped(0);
|
|
3480
3651
|
setError(null);
|
|
3652
|
+
setSubmitError(null);
|
|
3481
3653
|
};
|
|
3482
3654
|
const probe = async () => {
|
|
3483
3655
|
const value = source.trim();
|
|
3484
|
-
if (!value || phase === "probing") return;
|
|
3656
|
+
if (!value || phase === "probing" || pending) return;
|
|
3485
3657
|
setPhase("probing");
|
|
3486
3658
|
setError(null);
|
|
3659
|
+
setSubmitError(null);
|
|
3487
3660
|
try {
|
|
3488
3661
|
const result = await onProbe(value);
|
|
3489
3662
|
const found = Array.isArray(result?.skills) ? result.skills : [];
|
|
@@ -3496,20 +3669,33 @@ function AddSkillModal({ t, onCancel, onProbe, onInstall }) {
|
|
|
3496
3669
|
setPhase("failed");
|
|
3497
3670
|
}
|
|
3498
3671
|
};
|
|
3499
|
-
const toggle = (name) =>
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3672
|
+
const toggle = (name) => {
|
|
3673
|
+
setSubmitError(null);
|
|
3674
|
+
setSelected((prev) => {
|
|
3675
|
+
const next = new Set(prev);
|
|
3676
|
+
if (next.has(name)) next.delete(name);
|
|
3677
|
+
else next.add(name);
|
|
3678
|
+
return next;
|
|
3679
|
+
});
|
|
3680
|
+
};
|
|
3505
3681
|
const names = manual.split(/[,,\s]+/).map((item) => item.trim()).filter(Boolean);
|
|
3506
3682
|
const skippedText = skipped > 0 ? t("form.skipped").replace("{n}", String(skipped)) : null;
|
|
3507
3683
|
const manualMode = phase === "empty" || phase === "failed";
|
|
3508
3684
|
const skills = phase === "picked" ? [...selected] : names;
|
|
3509
3685
|
const canSubmit = phase === "picked" ? selected.size > 0 : manualMode ? names.length > 0 : false;
|
|
3510
|
-
const submit = () => {
|
|
3511
|
-
if (!canSubmit) return;
|
|
3512
|
-
|
|
3686
|
+
const submit = async () => {
|
|
3687
|
+
if (!canSubmit || pending) return;
|
|
3688
|
+
setPending(true);
|
|
3689
|
+
setSubmitError(null);
|
|
3690
|
+
let res;
|
|
3691
|
+
try {
|
|
3692
|
+
res = await onInstall({ source: source.trim(), skills });
|
|
3693
|
+
} catch (err) {
|
|
3694
|
+
res = { ok: false, error: err?.message || String(err) };
|
|
3695
|
+
}
|
|
3696
|
+
if (res?.ok) return;
|
|
3697
|
+
setPending(false);
|
|
3698
|
+
setSubmitError(res?.error || t("form.submitFailed"));
|
|
3513
3699
|
};
|
|
3514
3700
|
return h(
|
|
3515
3701
|
Overlay,
|
|
@@ -3517,14 +3703,16 @@ function AddSkillModal({ t, onCancel, onProbe, onInstall }) {
|
|
|
3517
3703
|
t,
|
|
3518
3704
|
title: t("action.addSkill"),
|
|
3519
3705
|
onCancel,
|
|
3706
|
+
// 提交安装进行中禁止关闭:请求不可取消,允许关闭只是假装取消
|
|
3707
|
+
dismissible: !pending,
|
|
3520
3708
|
footer: [
|
|
3521
|
-
h("button", { key: "c", className: "dshsc-btn ghost", onClick: onCancel }, t("action.cancel")),
|
|
3709
|
+
h("button", { key: "c", className: "dshsc-btn ghost", disabled: pending, onClick: onCancel }, t("action.cancel")),
|
|
3522
3710
|
h("button", {
|
|
3523
3711
|
key: "s",
|
|
3524
3712
|
className: "dshsc-btn primary",
|
|
3525
|
-
disabled: !canSubmit,
|
|
3526
|
-
onClick: submit
|
|
3527
|
-
}, t("action.save"))
|
|
3713
|
+
disabled: !canSubmit || pending,
|
|
3714
|
+
onClick: () => void submit()
|
|
3715
|
+
}, pending ? t("busy.working") : t("action.save"))
|
|
3528
3716
|
]
|
|
3529
3717
|
},
|
|
3530
3718
|
h("p", { className: "dshsc-sub" }, t("form.pickHint")),
|
|
@@ -3538,18 +3726,20 @@ function AddSkillModal({ t, onCancel, onProbe, onInstall }) {
|
|
|
3538
3726
|
h("input", {
|
|
3539
3727
|
value: source,
|
|
3540
3728
|
placeholder: t("form.sourceHint"),
|
|
3729
|
+
disabled: pending,
|
|
3541
3730
|
onChange: (event) => reset(event.target.value),
|
|
3542
3731
|
onKeyDown: (event) => {
|
|
3543
|
-
if (event.key === "Enter") void probe();
|
|
3732
|
+
if (event.key === "Enter" && !pending) void probe();
|
|
3544
3733
|
}
|
|
3545
3734
|
}),
|
|
3546
3735
|
h("button", {
|
|
3547
3736
|
className: "dshsc-btn",
|
|
3548
|
-
disabled: !source.trim() || phase === "probing",
|
|
3737
|
+
disabled: !source.trim() || phase === "probing" || pending,
|
|
3549
3738
|
onClick: () => void probe()
|
|
3550
3739
|
}, phase === "probing" ? t("form.probing") : t("form.probe"))
|
|
3551
3740
|
)
|
|
3552
3741
|
),
|
|
3742
|
+
submitError ? h("p", { className: "dshsc-form-error", role: "alert" }, submitError) : null,
|
|
3553
3743
|
phase === "picked" ? h(
|
|
3554
3744
|
"div",
|
|
3555
3745
|
{ key: "picked" },
|
|
@@ -3559,11 +3749,19 @@ function AddSkillModal({ t, onCancel, onProbe, onInstall }) {
|
|
|
3559
3749
|
{ style: { display: "flex", gap: 6, marginBottom: 6 } },
|
|
3560
3750
|
h("button", {
|
|
3561
3751
|
className: "dshsc-btn ghost",
|
|
3562
|
-
|
|
3752
|
+
disabled: pending,
|
|
3753
|
+
onClick: () => {
|
|
3754
|
+
setSubmitError(null);
|
|
3755
|
+
setSelected(new Set(options.map((item) => item.name)));
|
|
3756
|
+
}
|
|
3563
3757
|
}, t("form.selectAll")),
|
|
3564
3758
|
h("button", {
|
|
3565
3759
|
className: "dshsc-btn ghost",
|
|
3566
|
-
|
|
3760
|
+
disabled: pending,
|
|
3761
|
+
onClick: () => {
|
|
3762
|
+
setSubmitError(null);
|
|
3763
|
+
setSelected(/* @__PURE__ */ new Set());
|
|
3764
|
+
}
|
|
3567
3765
|
}, t("form.selectNone"))
|
|
3568
3766
|
),
|
|
3569
3767
|
h("div", { className: "dshsc-panel" }, options.map((option) => h(
|
|
@@ -3572,6 +3770,7 @@ function AddSkillModal({ t, onCancel, onProbe, onInstall }) {
|
|
|
3572
3770
|
h("input", {
|
|
3573
3771
|
type: "checkbox",
|
|
3574
3772
|
checked: selected.has(option.name),
|
|
3773
|
+
disabled: pending,
|
|
3575
3774
|
onChange: () => toggle(option.name)
|
|
3576
3775
|
}),
|
|
3577
3776
|
h(
|
|
@@ -3596,7 +3795,11 @@ function AddSkillModal({ t, onCancel, onProbe, onInstall }) {
|
|
|
3596
3795
|
h("input", {
|
|
3597
3796
|
value: manual,
|
|
3598
3797
|
placeholder: "git-kit, handoff",
|
|
3599
|
-
|
|
3798
|
+
disabled: pending,
|
|
3799
|
+
onChange: (event) => {
|
|
3800
|
+
setManual(event.target.value);
|
|
3801
|
+
if (submitError) setSubmitError(null);
|
|
3802
|
+
}
|
|
3600
3803
|
})
|
|
3601
3804
|
)
|
|
3602
3805
|
) : null,
|
|
@@ -3607,23 +3810,41 @@ function AddSkillModal({ t, onCancel, onProbe, onInstall }) {
|
|
|
3607
3810
|
// client/views/modals.js
|
|
3608
3811
|
function FormModal({ t, title, hint, fields, onCancel, onSubmit }) {
|
|
3609
3812
|
const [form, setForm] = import_react7.default.useState(() => Object.fromEntries(fields.map((field) => [field.key, ""])));
|
|
3813
|
+
const [pending, setPending] = import_react7.default.useState(false);
|
|
3814
|
+
const [error, setError] = import_react7.default.useState(null);
|
|
3610
3815
|
const incomplete = fields.some(
|
|
3611
3816
|
(field) => field.required !== false && !String(form[field.key] || "").trim()
|
|
3612
3817
|
);
|
|
3818
|
+
const submit = async () => {
|
|
3819
|
+
if (incomplete || pending) return;
|
|
3820
|
+
setPending(true);
|
|
3821
|
+
setError(null);
|
|
3822
|
+
let res;
|
|
3823
|
+
try {
|
|
3824
|
+
res = await onSubmit(form);
|
|
3825
|
+
} catch (err) {
|
|
3826
|
+
res = { ok: false, error: err?.message || String(err) };
|
|
3827
|
+
}
|
|
3828
|
+
if (res?.ok) return;
|
|
3829
|
+
setPending(false);
|
|
3830
|
+
setError(res?.error || t("form.submitFailed"));
|
|
3831
|
+
};
|
|
3613
3832
|
return h(
|
|
3614
3833
|
Overlay,
|
|
3615
3834
|
{
|
|
3616
3835
|
t,
|
|
3617
3836
|
title,
|
|
3618
3837
|
onCancel,
|
|
3838
|
+
// 提交进行中禁止关闭:请求不可取消,允许关闭只是假装取消
|
|
3839
|
+
dismissible: !pending,
|
|
3619
3840
|
footer: [
|
|
3620
|
-
h("button", { key: "c", className: "dshsc-btn ghost", onClick: onCancel }, t("action.cancel")),
|
|
3841
|
+
h("button", { key: "c", className: "dshsc-btn ghost", disabled: pending, onClick: onCancel }, t("action.cancel")),
|
|
3621
3842
|
h("button", {
|
|
3622
3843
|
key: "s",
|
|
3623
3844
|
className: "dshsc-btn primary",
|
|
3624
|
-
disabled: incomplete,
|
|
3625
|
-
onClick: () =>
|
|
3626
|
-
}, t("action.save"))
|
|
3845
|
+
disabled: incomplete || pending,
|
|
3846
|
+
onClick: () => void submit()
|
|
3847
|
+
}, pending ? t("busy.working") : t("action.save"))
|
|
3627
3848
|
]
|
|
3628
3849
|
},
|
|
3629
3850
|
hint ? h("p", { className: "dshsc-sub" }, hint) : null,
|
|
@@ -3634,9 +3855,14 @@ function FormModal({ t, title, hint, fields, onCancel, onSubmit }) {
|
|
|
3634
3855
|
h("input", {
|
|
3635
3856
|
value: form[field.key],
|
|
3636
3857
|
placeholder: field.placeholder,
|
|
3637
|
-
|
|
3858
|
+
disabled: pending,
|
|
3859
|
+
onChange: (event) => {
|
|
3860
|
+
setForm((prev) => ({ ...prev, [field.key]: event.target.value }));
|
|
3861
|
+
if (error) setError(null);
|
|
3862
|
+
}
|
|
3638
3863
|
})
|
|
3639
|
-
))
|
|
3864
|
+
)),
|
|
3865
|
+
error ? h("p", { className: "dshsc-form-error", role: "alert" }, error) : null
|
|
3640
3866
|
);
|
|
3641
3867
|
}
|
|
3642
3868
|
function ConfirmModal({ t, title, body, onCancel, onConfirm, danger, confirmLabel, hideCancel = false }) {
|
|
@@ -3751,7 +3977,7 @@ function Modals({ t, modal, data, autoLinkAgents, actions, onClose, onSaveAutoLi
|
|
|
3751
3977
|
{ key: "subpath", label: t("form.subpath"), placeholder: "skills", required: false }
|
|
3752
3978
|
],
|
|
3753
3979
|
onCancel: onClose,
|
|
3754
|
-
onSubmit: (form) =>
|
|
3980
|
+
onSubmit: (form) => actions.addSubscription(form)
|
|
3755
3981
|
});
|
|
3756
3982
|
}
|
|
3757
3983
|
if (modal.type === "exports") {
|
|
@@ -4281,6 +4507,10 @@ var STATUS_KEY = {
|
|
|
4281
4507
|
"broken-link": "status.broken",
|
|
4282
4508
|
unmanaged: "status.unmanaged"
|
|
4283
4509
|
};
|
|
4510
|
+
var STATUS_HINT = {
|
|
4511
|
+
"locally-modified": "status.localHint",
|
|
4512
|
+
conflict: "status.conflictHint"
|
|
4513
|
+
};
|
|
4284
4514
|
function TabButton({ id, label, count, active, onSelect }) {
|
|
4285
4515
|
return h("button", {
|
|
4286
4516
|
type: "button",
|
|
@@ -4474,7 +4704,10 @@ function SkillReader({
|
|
|
4474
4704
|
"div",
|
|
4475
4705
|
{ className: "dshsc-reader-title" },
|
|
4476
4706
|
h("h2", { id: "dshsc-reader-name", tabIndex: -1 }, name || t("viewer.title")),
|
|
4477
|
-
row?.status && STATUS_KEY[row.status] ? h("span", {
|
|
4707
|
+
row?.status && STATUS_KEY[row.status] ? h("span", {
|
|
4708
|
+
className: `dshsc-tag ${row.status === "current" ? "ok" : "warn"}`,
|
|
4709
|
+
title: STATUS_HINT[row.status] ? t(STATUS_HINT[row.status]) : void 0
|
|
4710
|
+
}, t(STATUS_KEY[row.status])) : null,
|
|
4478
4711
|
row?.enabled === false ? h("span", { className: "dshsc-tag off" }, t("status.off")) : null,
|
|
4479
4712
|
variant === "drawer" ? h("button", {
|
|
4480
4713
|
type: "button",
|
|
@@ -4848,25 +5081,122 @@ function SkillReader({
|
|
|
4848
5081
|
), overlayHost());
|
|
4849
5082
|
}
|
|
4850
5083
|
|
|
5084
|
+
// client/views/plugin-update-modal.js
|
|
5085
|
+
var import_react9 = __toESM(require("react"), 1);
|
|
5086
|
+
function PluginUpdateModal({ t, update, onClose, repoUrl }) {
|
|
5087
|
+
const { status, checking, applying, checkError, applyError, check, apply: apply2, release, loadRelease } = update;
|
|
5088
|
+
const [copied, setCopied] = import_react9.default.useState(false);
|
|
5089
|
+
import_react9.default.useEffect(() => {
|
|
5090
|
+
void check();
|
|
5091
|
+
}, [check]);
|
|
5092
|
+
const latest = status?.latestVersion || null;
|
|
5093
|
+
const current = status?.currentVersion || "—";
|
|
5094
|
+
const available = Boolean(status?.updateAvailable);
|
|
5095
|
+
const canAuto = status?.canAutoUpdate !== false;
|
|
5096
|
+
const manual = status?.manualCommand || "";
|
|
5097
|
+
const restartRequired = status?.restartRequired === true;
|
|
5098
|
+
const updatedVersion = status?.updatedVersion || null;
|
|
5099
|
+
import_react9.default.useEffect(() => {
|
|
5100
|
+
if (available && latest) void loadRelease(latest);
|
|
5101
|
+
}, [available, latest, loadRelease]);
|
|
5102
|
+
const releaseData = release?.version === latest ? release.data : null;
|
|
5103
|
+
const releaseLoading = release?.version === latest && release.loading;
|
|
5104
|
+
const releaseBody = typeof releaseData?.body === "string" ? releaseData.body.trim() : "";
|
|
5105
|
+
const releaseUrl = releaseData?.htmlUrl || (repoUrl ? `${repoUrl}/releases` : null);
|
|
5106
|
+
const copy = () => {
|
|
5107
|
+
if (!manual || typeof navigator === "undefined" || !navigator.clipboard?.writeText) return;
|
|
5108
|
+
Promise.resolve(navigator.clipboard.writeText(manual)).then(() => {
|
|
5109
|
+
setCopied(true);
|
|
5110
|
+
setTimeout(() => setCopied(false), 1500);
|
|
5111
|
+
}).catch(() => {
|
|
5112
|
+
});
|
|
5113
|
+
};
|
|
5114
|
+
let stateLine;
|
|
5115
|
+
if (checking && !status) stateLine = t("pluginUpdate.checking");
|
|
5116
|
+
else if (checkError || status?.checkFailed) stateLine = t("pluginUpdate.checkFailed");
|
|
5117
|
+
else if (restartRequired) stateLine = t("pluginUpdate.updated", { version: updatedVersion || latest });
|
|
5118
|
+
else if (available) stateLine = t("pluginUpdate.available");
|
|
5119
|
+
else if (status) stateLine = t("pluginUpdate.upToDate");
|
|
5120
|
+
else stateLine = t("pluginUpdate.checking");
|
|
5121
|
+
const stateTone = checkError || status?.checkFailed ? "err" : available ? "update" : "ok";
|
|
5122
|
+
const footer = [
|
|
5123
|
+
h("button", {
|
|
5124
|
+
key: "check",
|
|
5125
|
+
className: "dshsc-btn ghost",
|
|
5126
|
+
disabled: checking || applying,
|
|
5127
|
+
onClick: () => void check(true)
|
|
5128
|
+
}, h(RefreshIcon, { size: 13 }), t("pluginUpdate.recheck"))
|
|
5129
|
+
];
|
|
5130
|
+
if (available && canAuto) {
|
|
5131
|
+
footer.push(h("button", {
|
|
5132
|
+
key: "apply",
|
|
5133
|
+
className: "dshsc-btn primary",
|
|
5134
|
+
disabled: applying,
|
|
5135
|
+
onClick: () => void apply2()
|
|
5136
|
+
}, applying ? t("pluginUpdate.applying") : t("pluginUpdate.update", { version: latest })));
|
|
5137
|
+
}
|
|
5138
|
+
footer.push(h("button", { key: "close", className: "dshsc-btn ghost", onClick: onClose }, t("pluginUpdate.close")));
|
|
5139
|
+
return h(
|
|
5140
|
+
Overlay,
|
|
5141
|
+
{ t, title: t("pluginUpdate.title"), onCancel: onClose, footer },
|
|
5142
|
+
h(
|
|
5143
|
+
"dl",
|
|
5144
|
+
{ className: "dshsc-kv" },
|
|
5145
|
+
h("dt", null, t("pluginUpdate.current")),
|
|
5146
|
+
h("dd", null, h("span", { className: "dshsc-code" }, current)),
|
|
5147
|
+
h("dt", null, t("pluginUpdate.latest")),
|
|
5148
|
+
h("dd", null, h("span", { className: "dshsc-code" }, latest || "—"))
|
|
5149
|
+
),
|
|
5150
|
+
h("p", { className: "dshsc-sub", "data-kind": stateTone, role: "status", style: stateTone === "err" ? { color: "var(--dsw-alias-state-error-primary)" } : null }, stateLine),
|
|
5151
|
+
// 有新版本:展示该版本的发布说明(拉了 GitHub Release 正文就渲染,拉不到给外链)
|
|
5152
|
+
available ? h(
|
|
5153
|
+
"div",
|
|
5154
|
+
{ className: "dshsc-release" },
|
|
5155
|
+
h("div", { className: "dshsc-release-head" }, t("pluginUpdate.whatsNew", { version: latest })),
|
|
5156
|
+
releaseLoading ? h("p", { className: "dshsc-sub" }, t("pluginUpdate.releaseLoading")) : releaseBody ? h("div", { className: "dshsc-release-body" }, h(Markdown, { text: releaseBody })) : h(
|
|
5157
|
+
"p",
|
|
5158
|
+
{ className: "dshsc-sub" },
|
|
5159
|
+
releaseUrl ? h("a", { className: "dshsc-md-link", href: releaseUrl, target: "_blank", rel: "noopener noreferrer" }, t("pluginUpdate.viewReleaseNotes")) : t("pluginUpdate.releaseUnavailable")
|
|
5160
|
+
)
|
|
5161
|
+
) : null,
|
|
5162
|
+
applyError ? h("p", { className: "dshsc-sub", role: "alert", style: { color: "var(--dsw-alias-state-error-primary)" } }, applyError.message) : null,
|
|
5163
|
+
restartRequired ? h("p", { className: "dshsc-sub" }, t("pluginUpdate.restartHint")) : null,
|
|
5164
|
+
// 一键更新不可用(或本次安装失败后想手动兜底):给命令 + 复制
|
|
5165
|
+
!canAuto && manual ? h(
|
|
5166
|
+
"div",
|
|
5167
|
+
{ className: "dshsc-field" },
|
|
5168
|
+
h("span", null, t("pluginUpdate.manual")),
|
|
5169
|
+
h(
|
|
5170
|
+
"div",
|
|
5171
|
+
{ className: "dshsc-inline", style: { display: "flex", gap: 8, alignItems: "center" } },
|
|
5172
|
+
h("code", { className: "dshsc-code", style: { flex: 1, wordBreak: "break-all" } }, manual),
|
|
5173
|
+
h("button", { className: "dshsc-btn ghost", onClick: copy }, copied ? t("pluginUpdate.copied") : t("pluginUpdate.copy"))
|
|
5174
|
+
)
|
|
5175
|
+
) : null
|
|
5176
|
+
);
|
|
5177
|
+
}
|
|
5178
|
+
|
|
4851
5179
|
// client/section.js
|
|
4852
5180
|
var UPDATABLE2 = /* @__PURE__ */ new Set(["update-available", "locally-modified", "conflict"]);
|
|
4853
5181
|
function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
4854
5182
|
const { data, error, busy, toast, setToast, setBusy, reload, run } = useSkillsData();
|
|
4855
|
-
const [tab, setTab] =
|
|
4856
|
-
const [view, setView] =
|
|
4857
|
-
const [modal, setModal] =
|
|
4858
|
-
const [expanded, setExpanded] =
|
|
4859
|
-
const [placementBusy, setPlacementBusy] =
|
|
4860
|
-
const [placementOverride, setPlacementOverride] =
|
|
4861
|
-
const [query, setQuery] =
|
|
4862
|
-
const [viewer, setViewer] =
|
|
4863
|
-
const rootRef =
|
|
4864
|
-
const scrollPos =
|
|
4865
|
-
const restoreScroll =
|
|
4866
|
-
const [filter, setFilter] =
|
|
4867
|
-
const [builtinOpen, setBuiltinOpen] =
|
|
5183
|
+
const [tab, setTab] = import_react10.default.useState("installed");
|
|
5184
|
+
const [view, setView] = import_react10.default.useState("flat");
|
|
5185
|
+
const [modal, setModal] = import_react10.default.useState(null);
|
|
5186
|
+
const [expanded, setExpanded] = import_react10.default.useState({});
|
|
5187
|
+
const [placementBusy, setPlacementBusy] = import_react10.default.useState(null);
|
|
5188
|
+
const [placementOverride, setPlacementOverride] = import_react10.default.useState(null);
|
|
5189
|
+
const [query, setQuery] = import_react10.default.useState("");
|
|
5190
|
+
const [viewer, setViewer] = import_react10.default.useState(null);
|
|
5191
|
+
const rootRef = import_react10.default.useRef(null);
|
|
5192
|
+
const scrollPos = import_react10.default.useRef(null);
|
|
5193
|
+
const restoreScroll = import_react10.default.useRef(null);
|
|
5194
|
+
const [filter, setFilter] = import_react10.default.useState("all");
|
|
5195
|
+
const [builtinOpen, setBuiltinOpen] = import_react10.default.useState(false);
|
|
4868
5196
|
const { settingsOpen, settingsRef, settingsPlacement, closeSettings, toggleSettings } = useSettingsPopover();
|
|
4869
5197
|
const { agentTip, agentTipPos, agentTipRef, openAgentTip, closeAgentTip } = useAgentTip();
|
|
5198
|
+
const pluginUpdate = usePluginUpdate();
|
|
5199
|
+
const [updateOpen, setUpdateOpen] = import_react10.default.useState(false);
|
|
4870
5200
|
const rows = data?.installs || [];
|
|
4871
5201
|
const actions = useSkillActions({ t, rows, reload, run, setBusy, setToast, setModal });
|
|
4872
5202
|
const writable = Boolean(data) && !data?.readOnly && data?.cli?.usable;
|
|
@@ -4887,7 +5217,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
4887
5217
|
const agentLabels = row.exports.map((item) => item.label);
|
|
4888
5218
|
return [row.name, row.source || "", ...agentLabels].join(" ").toLowerCase().includes(queryText);
|
|
4889
5219
|
});
|
|
4890
|
-
|
|
5220
|
+
import_react10.default.useEffect(() => {
|
|
4891
5221
|
if (!viewer || !data) return;
|
|
4892
5222
|
if (rows.some((row) => row.name === viewer.name)) return;
|
|
4893
5223
|
setViewer(null);
|
|
@@ -4921,7 +5251,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
4921
5251
|
if (viewer?.mode === "page") restoreScroll.current = scrollPos.current;
|
|
4922
5252
|
setViewer(null);
|
|
4923
5253
|
}
|
|
4924
|
-
|
|
5254
|
+
import_react10.default.useLayoutEffect(() => {
|
|
4925
5255
|
if (restoreScroll.current === null) return;
|
|
4926
5256
|
const target = restoreScroll.current;
|
|
4927
5257
|
restoreScroll.current = null;
|
|
@@ -4990,7 +5320,7 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
4990
5320
|
onToast: (next) => setToast(next)
|
|
4991
5321
|
}) : null;
|
|
4992
5322
|
const listNode = h(
|
|
4993
|
-
|
|
5323
|
+
import_react10.default.Fragment,
|
|
4994
5324
|
null,
|
|
4995
5325
|
h(Header, {
|
|
4996
5326
|
t,
|
|
@@ -5006,6 +5336,8 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
5006
5336
|
placement,
|
|
5007
5337
|
placementBusy,
|
|
5008
5338
|
readOnly: Boolean(data?.readOnly),
|
|
5339
|
+
pluginUpdate,
|
|
5340
|
+
onCheckUpdate: () => setUpdateOpen(true),
|
|
5009
5341
|
onToggleSettings: toggleSettings,
|
|
5010
5342
|
onChangePlacement: (next) => void changePlacement(next),
|
|
5011
5343
|
onOpenAutoLink: () => setModal({ type: "autolink" }),
|
|
@@ -5075,7 +5407,8 @@ function Section({ t, page = false, pickDirectory, onPlacementChange }) {
|
|
|
5075
5407
|
onClose: () => setModal(null),
|
|
5076
5408
|
onSaveAutoLink: saveAutoLink
|
|
5077
5409
|
}) : null,
|
|
5078
|
-
agentTip ? h(AgentTip, { t, agentTip, agentTipPos, nodeRef: agentTipRef }) : null
|
|
5410
|
+
agentTip ? h(AgentTip, { t, agentTip, agentTipPos, nodeRef: agentTipRef }) : null,
|
|
5411
|
+
updateOpen ? h(PluginUpdateModal, { t, update: pluginUpdate, repoUrl: GITHUB_URL, onClose: () => setUpdateOpen(false) }) : null
|
|
5079
5412
|
);
|
|
5080
5413
|
return page ? h("div", { className: "dshsc-page" }, root) : root;
|
|
5081
5414
|
}
|