@noob-stupid/dsh-plugin-console 0.3.8
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/LICENSE +21 -0
- package/README.md +446 -0
- package/README.zh.md +401 -0
- package/cordis.patch.yml +5 -0
- package/lib/client.js +2570 -0
- package/lib/index.js +3703 -0
- package/package.json +39 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,2570 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@deepseek-ai/dsh-plugin-console",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react = require("react");
|
|
8
|
+
//#region styles
|
|
9
|
+
const css = ".pc_section{width:100%;max-width:760px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:14px;display:flex}.pc_section h3{margin:0;font-size:13px;font-weight:600;line-height:20px}.pc_message{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px;margin:0}.pc_messageRow{display:flex;align-items:center;gap:10px}.pc_spinner{width:14px;height:14px;border:2px solid var(--dsw-alias-border-l2);border-top-color:var(--dsw-alias-state-business-primary);border-radius:50%;animation:pcspin 1s linear infinite;flex:none}@keyframes pcspin{to{transform:rotate(360deg)}}.pc_message[data-error=true]{color:var(--dsw-alias-state-error-primary)}.pc_list{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin:0;padding:0;list-style:none;display:grid}.pc_row{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;min-width:0;padding:10px 12px;flex-direction:column;gap:6px;display:flex}.pc_rowTop{align-items:center;gap:8px;display:flex}.pc_name{font-size:13px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.pc_tag{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);border-radius:999px;padding:1px 8px;font-size:11px;line-height:16px;flex:none}.pc_tag[data-enabled=true]{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}.pc_tag[data-user=true]{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_tag[data-skill=true]{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary)}.pc_tag[data-system=true]{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-layer-2)}.pc_tag[data-disabled=true]{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.pc_tag[data-suite=true]{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}.pc_tag[data-error=true]{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.pc_tabGroup{display:inline-flex;gap:4px;align-items:center}.pc_meta{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;gap:6px;display:flex;align-items:center}.pc_phase[data-phase=failed]{color:var(--dsw-alias-state-error-primary)}.pc_phase[data-phase=active]{color:var(--dsw-alias-state-success-primary)}.pc_toggle{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:0 0;font:inherit;cursor:pointer;border-radius:6px;padding:4px 12px;align-self:flex-start}.pc_toggle:hover:not(:disabled){border-color:var(--dsw-alias-state-business-primary)}.pc_toggle:disabled{opacity:.5;cursor:default}.pc_search{gap:8px;display:flex}.pc_search input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);height:34px;flex:1;color:var(--dsw-alias-label-primary);font:inherit;border-radius:8px;outline:none;padding:0 12px;font-size:13px}.pc_search input:focus-visible{border-color:var(--dsw-alias-state-business-primary)}.pc_search button{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);font:inherit;cursor:pointer;border-radius:8px;padding:0 14px;font-size:13px}.pc_search button:hover{border-color:var(--dsw-alias-state-business-primary)}.pc_market{flex-direction:column;gap:10px;margin:0;padding:0;list-style:none;display:flex}.pc_item{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;min-width:0;padding:10px 12px;flex-direction:column;gap:6px;display:flex}.pc_itemTop{align-items:center;gap:8px;display:flex}.pc_itemTop a{color:var(--dsw-alias-state-business-primary);font-size:13px;font-weight:600;text-decoration:none;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pc_stars{color:var(--dsw-alias-label-tertiary);font-size:11px;flex:none}.pc_desc{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;margin:0}.pc_item button{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:0 0;font:inherit;cursor:pointer;border-radius:6px;padding:3px 10px;align-self:flex-start;font-size:12px}.pc_item button:hover:not(:disabled){border-color:var(--dsw-alias-state-business-primary)}.pc_item button:disabled{opacity:.5;cursor:default}.pc_detail{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;padding:10px 12px;flex-direction:column;gap:6px;display:flex}.pc_status{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;margin:0}.pc_note{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;margin:0}.pc_marketHead{display:flex;align-items:center;justify-content:space-between;gap:10px}.pc_descWrap{flex-direction:column;display:flex}.pc_installedHead{display:flex;align-items:center;gap:10px}.pc_backTopWrap{position:fixed;top:56px;right:20px;z-index:100;display:flex;flex-direction:column;gap:8px}.pc_backTop{width:34px;height:34px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:rgba(22,27,34,.72);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);cursor:pointer;backdrop-filter:blur(4px);opacity:.7}.pc_backTop:hover{opacity:1;border-color:var(--dsw-alias-state-business-primary)}.pc_descTopbar{display:flex;justify-content:flex-end;margin:2px 0}.pc_ghpill{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);border-radius:999px;padding:1px 10px;font-size:11px;line-height:18px}.pc_ghpill[data-on=true]{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}.pc_extraBtn{opacity:.75;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:0 0;font:inherit;cursor:pointer;border-radius:6px;padding:4px 10px;font-size:12px;line-height:16px;flex:none;transition:opacity .15s ease,border-color 1.5s ease,color 1.5s ease}.pc_extraBtn:hover{opacity:1}.pc_extraBtn[data-active=true]{opacity:1;border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_extraBtn[data-flash=true]{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_view{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:0 0;font:inherit;cursor:pointer;border-radius:6px;padding:2px 10px;font-size:12px;line-height:18px;flex:none}.pc_floatPanel{position:fixed;left:20px;top:50%;transform:translateY(-50%);width:340px;max-height:75vh;overflow-y:auto;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;padding:12px;box-shadow:0 8px 24px rgba(0,0,0,.18);z-index:50;flex-direction:column;gap:8px;display:flex}.pc_refresh{position:sticky;bottom:14px;align-self:flex-end;margin-top:8px;z-index:60;opacity:.45;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:transparent;font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:opacity .15s ease}.pc_refresh:hover{opacity:.9}.pc_date{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;flex:none}.pc_searchInner{position:relative;flex:1;display:flex;align-items:center;min-width:0}.pc_searchInner input{padding-right:32px;width:100%}.pc_starBtn{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-tertiary);cursor:pointer;font-size:13px;line-height:1;padding:4px 8px;border-radius:6px;opacity:.75;transition:opacity .15s ease;flex:none}.pc_starBtn:hover{opacity:1}.pc_starBtn[data-active=true]{opacity:1;color:var(--dsw-alias-state-business-primary)}.pc_restartBtn{position:fixed;right:20px;top:50%;transform:translateY(-50%);z-index:60;opacity:.45;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:transparent;font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:opacity .15s ease}.pc_restartBtn:hover{opacity:.9}.pc_relaunchFloat{position:fixed;left:20px;top:50%;transform:translateY(-50%);z-index:70;opacity:.85;border:1px solid var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-bg-layer-3);font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:opacity .15s ease}.pc_relaunchFloat:hover{opacity:1}.pc_aiToggle{position:fixed;right:20px;top:20px;z-index:70;opacity:.45;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:transparent;font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:opacity .15s ease}.pc_aiToggle:hover{opacity:.9}.pc_aiToggle[data-active=true]{opacity:1;border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_sourcesFloat{position:fixed;right:20px;top:76px;z-index:70;opacity:.85;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:opacity .15s ease}.pc_sourcesFloat:hover{opacity:1;border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_modeFloat{position:fixed;right:20px;top:176px;z-index:70;opacity:.85;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:opacity .15s ease}.pc_modeFloat:hover{opacity:1;border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_modeFloat[data-mode=true]{opacity:1;border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary)}.pc_trashBtn{border:0;background:0 0;color:var(--dsw-alias-label-tertiary);cursor:pointer;font-size:12px;line-height:16px;padding:2px 6px;flex:none;display:flex;align-items:center;border-radius:6px;transition:color .15s ease}.pc_trashBtn:hover{color:var(--dsw-alias-state-error-primary)}.pc_consentRemember{display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;color:var(--dsw-alias-label-secondary);cursor:pointer}.pc_modalBackdrop{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:1000;display:flex;align-items:center;justify-content:center}.pc_modalCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:12px;padding:16px 18px;max-width:420px;width:calc(100% - 48px);flex-direction:column;gap:10px;display:flex;box-shadow:0 12px 32px rgba(0,0,0,.25)}.pc_ghpill{background:0 0;cursor:pointer}.pc_ghwrap{position:relative;display:inline-flex}.pc_sourceMenu{position:absolute;right:0;top:calc(100% + 6px);z-index:90;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:8px;padding:4px;flex-direction:column;display:flex;min-width:120px;box-shadow:0 8px 24px rgba(0,0,0,.18)}.pc_sourceOpt{border:0;background:0 0;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;border-radius:6px;padding:6px 10px;text-align:left;font-size:12px;line-height:16px}.pc_sourceOpt[data-active=true]{color:var(--dsw-alias-state-business-primary);font-weight:600}.pc_sourceOpt:hover{background:var(--dsw-alias-bg-layer-3)}";
|
|
10
|
+
const tagId = "@deepseek-ai/dsh-plugin-console/PluginConsoleTab.css";
|
|
11
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
12
|
+
const tag = document.createElement("style");
|
|
13
|
+
tag.dataset.plugin = "@deepseek-ai/dsh-plugin-console";
|
|
14
|
+
tag.dataset.pluginCss = tagId;
|
|
15
|
+
tag.textContent = css;
|
|
16
|
+
document.head.appendChild(tag);
|
|
17
|
+
}
|
|
18
|
+
const styles = {
|
|
19
|
+
section: "pc_section",
|
|
20
|
+
marketHead: "pc_marketHead",
|
|
21
|
+
installedHead: "pc_installedHead",
|
|
22
|
+
backTop: "pc_backTop",
|
|
23
|
+
backTopWrap: "pc_backTopWrap",
|
|
24
|
+
spinner: "pc_spinner",
|
|
25
|
+
ghpill: "pc_ghpill",
|
|
26
|
+
descWrap: "pc_descWrap",
|
|
27
|
+
descTopbar: "pc_descTopbar",
|
|
28
|
+
message: "pc_message",
|
|
29
|
+
list: "pc_list",
|
|
30
|
+
row: "pc_row",
|
|
31
|
+
rowTop: "pc_rowTop",
|
|
32
|
+
name: "pc_name",
|
|
33
|
+
tag: "pc_tag",
|
|
34
|
+
meta: "pc_meta",
|
|
35
|
+
phase: "pc_phase",
|
|
36
|
+
toggle: "pc_toggle",
|
|
37
|
+
search: "pc_search",
|
|
38
|
+
extraBtn: "pc_extraBtn",
|
|
39
|
+
view: "pc_view",
|
|
40
|
+
floatPanel: "pc_floatPanel",
|
|
41
|
+
refresh: "pc_refresh",
|
|
42
|
+
restartBtn: "pc_restartBtn",
|
|
43
|
+
relaunchFloat: "pc_relaunchFloat",
|
|
44
|
+
aiToggle: "pc_aiToggle",
|
|
45
|
+
sourcesFloat: "pc_sourcesFloat",
|
|
46
|
+
modeFloat: "pc_modeFloat",
|
|
47
|
+
trashBtn: "pc_trashBtn",
|
|
48
|
+
consentRemember: "pc_consentRemember",
|
|
49
|
+
modalBackdrop: "pc_modalBackdrop",
|
|
50
|
+
modalCard: "pc_modalCard",
|
|
51
|
+
ghwrap: "pc_ghwrap",
|
|
52
|
+
sourceMenu: "pc_sourceMenu",
|
|
53
|
+
sourceOpt: "pc_sourceOpt",
|
|
54
|
+
date: "pc_date",
|
|
55
|
+
searchInner: "pc_searchInner",
|
|
56
|
+
starBtn: "pc_starBtn",
|
|
57
|
+
tabGroup: "pc_tabGroup",
|
|
58
|
+
market: "pc_market",
|
|
59
|
+
item: "pc_item",
|
|
60
|
+
itemTop: "pc_itemTop",
|
|
61
|
+
stars: "pc_stars",
|
|
62
|
+
desc: "pc_desc",
|
|
63
|
+
detail: "pc_detail",
|
|
64
|
+
status: "pc_status",
|
|
65
|
+
note: "pc_note",
|
|
66
|
+
};
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region locales
|
|
69
|
+
const zh = {
|
|
70
|
+
tab: "插件管理",
|
|
71
|
+
loading: "正在读取插件…",
|
|
72
|
+
error: "暂时无法读取插件。",
|
|
73
|
+
retry: "重试",
|
|
74
|
+
installedTitle: "已安装插件",
|
|
75
|
+
marketTitle: "插件市场(GitHub)",
|
|
76
|
+
marketTitleOther: "插件市场({source})",
|
|
77
|
+
search: "搜索",
|
|
78
|
+
searchPlaceholder: "搜索 GitHub 上的 dsh-plugin 插件(留空使用默认关键词)",
|
|
79
|
+
searchPlaceholderOther: "搜索 {source} 上的插件(留空使用默认关键词)",
|
|
80
|
+
multiSourceTitle: "多源汇总:GitHub + 全部自定义源并行搜索(Gitee 直装模式不参与)",
|
|
81
|
+
multiSourceToggle: "多源搜索开关",
|
|
82
|
+
multiEmpty: "所有搜索源都没有找到匹配的插件。",
|
|
83
|
+
giteeRepoPlaceholder: "输入 Gitee 仓库名(owner/repo,如 oschina/git-osc)",
|
|
84
|
+
giteeEmpty: "Gitee 官方搜索接口已废弃:请在搜索框直接输入仓库名(owner/repo,如 oschina/git-osc)后回车",
|
|
85
|
+
empty: "暂无插件。",
|
|
86
|
+
on: "启用",
|
|
87
|
+
off: "停用",
|
|
88
|
+
enabledTag: "已启用",
|
|
89
|
+
disabledTag: "已停用",
|
|
90
|
+
userDisabledTag: "补丁停用",
|
|
91
|
+
userForcedTag: "补丁强制启用",
|
|
92
|
+
protectedTag: "受保护",
|
|
93
|
+
unobserved: "未挂载",
|
|
94
|
+
pending: "等待依赖",
|
|
95
|
+
loadingPhase: "加载中",
|
|
96
|
+
active: "已挂载",
|
|
97
|
+
failed: "挂载失败",
|
|
98
|
+
unloading: "卸载中",
|
|
99
|
+
toggledOn: "已请求启用",
|
|
100
|
+
toggledOff: "已请求停用",
|
|
101
|
+
failed: "操作失败",
|
|
102
|
+
view: "查看",
|
|
103
|
+
install: "添加并启用",
|
|
104
|
+
installing: "正在安装…",
|
|
105
|
+
marketLoading: "正在搜索 GitHub…",
|
|
106
|
+
marketLoadingOther: "正在搜索 {source}…",
|
|
107
|
+
directOther: "通过服务端通道检索 {source} 平台(自定义源不走浏览器直连)",
|
|
108
|
+
marketError: "搜索失败",
|
|
109
|
+
marketEmpty: "没有找到相关项目。",
|
|
110
|
+
repoLoading: "正在读取仓库信息…",
|
|
111
|
+
repoError: "读取仓库信息失败",
|
|
112
|
+
packageName: "npm 包名",
|
|
113
|
+
dshHint: "疑似 DSH 插件",
|
|
114
|
+
noPackage: "该仓库没有 package.json,无法安装",
|
|
115
|
+
privateRootHint: "该仓库根包未发布到 npm(private),无法直接安装",
|
|
116
|
+
subpackagesTitle: "仓库子包(可单独安装)",
|
|
117
|
+
subpackagesLoading: "正在读取子包列表…",
|
|
118
|
+
subpackagesError: "读取子包列表失败(网络波动时可稍后重试)",
|
|
119
|
+
subpackagesEmpty: "未发现可安装的子包。",
|
|
120
|
+
recentFailures: "最近安装失败",
|
|
121
|
+
installed: "已安装并启用",
|
|
122
|
+
installNote: "HMR 正在生效;若插件带界面,请刷新页面",
|
|
123
|
+
bundleNote: "该包是 bundle 层插件(如皮肤合集),重启 DSH 服务后生效",
|
|
124
|
+
direct: "(浏览器直连 GitHub;若你的浏览器打不开 GitHub,将自动回退到服务端通道)",
|
|
125
|
+
details: "详情",
|
|
126
|
+
expandDesc: "展开",
|
|
127
|
+
collapseDesc: "收起",
|
|
128
|
+
addLocal: "添加到本地",
|
|
129
|
+
installingLocal: "安装中…",
|
|
130
|
+
needPackage: "该仓库没有 package.json,无法直接添加",
|
|
131
|
+
noDetail: "该插件没有提供说明信息。",
|
|
132
|
+
versionLabel: "版本",
|
|
133
|
+
repoLabel: "仓库",
|
|
134
|
+
homepageLabel: "主页",
|
|
135
|
+
readmeLabel: "说明摘要",
|
|
136
|
+
loadingDetails: "正在读取详情…",
|
|
137
|
+
githubLoggedIn: "已登录 GitHub:",
|
|
138
|
+
githubCornerOut: "未登录 GitHub",
|
|
139
|
+
searchInstalledPlaceholder: "搜索已安装插件(名称或 id)",
|
|
140
|
+
extraFilter: "已装",
|
|
141
|
+
extraFilterAll: "全部",
|
|
142
|
+
extraTag: "第三方",
|
|
143
|
+
extraFilterTitle: "切换:已装(后装/第三方插件)/ 全部",
|
|
144
|
+
extraEmpty: "没有已装的插件(非 dsh 自带)。",
|
|
145
|
+
noMatch: "没有匹配的插件。",
|
|
146
|
+
loadMore: "加载更多",
|
|
147
|
+
loadingMore: "加载中…",
|
|
148
|
+
backToSearch: "回到搜索",
|
|
149
|
+
collapseResults: "收起搜索结果",
|
|
150
|
+
expandResults: "展开搜索结果",
|
|
151
|
+
reloadPage: "刷新页面",
|
|
152
|
+
refreshList: "刷新插件列表",
|
|
153
|
+
stageLabel: "阶段",
|
|
154
|
+
stagePreparing: "准备中",
|
|
155
|
+
stageInstalling: "下载安装中(可离开本页,后台继续)",
|
|
156
|
+
stageConfiguring: "写入启用配置",
|
|
157
|
+
stageRepairing: "本地 AI 接管安装中…",
|
|
158
|
+
restartService: "重启服务",
|
|
159
|
+
restarting: "服务重启中,页面稍后自动恢复…",
|
|
160
|
+
autoReload: "页面即将自动刷新…",
|
|
161
|
+
aiRepaired: "本地 AI 已接管并完成修复,请刷新页面查看",
|
|
162
|
+
deletePlugin: "删除插件(移除配置并卸载包)",
|
|
163
|
+
confirmDelete: "确认删除?",
|
|
164
|
+
deleting: "删除中…",
|
|
165
|
+
deleteNote: "已删除插件并卸载包,正在刷新",
|
|
166
|
+
deleteBundleNote: "已移除插件所属 bundle,重启服务后生效",
|
|
167
|
+
aiConsentText: "常规安装通道均已尝试失败。下一步将让本地 AI 接管安装——这会调用 DeepSeek API 模型,可能产生 API 费用。是否继续?",
|
|
168
|
+
aiConsentNotifyTitle: "插件安装需要授权",
|
|
169
|
+
aiConsentApprove: "同意,继续(可能产生费用)",
|
|
170
|
+
aiConsentDecline: "取消",
|
|
171
|
+
aiFallbackLabel: "AI 兜底",
|
|
172
|
+
aiFallbackTitle: "关闭后:常规通道失败将直接取消安装,不再调用模型 API(零费用)",
|
|
173
|
+
aiFallbackOff: "已按设置关闭 AI 兜底,本次安装已取消(零费用)",
|
|
174
|
+
aiConsentRemember: "以后不再提醒(自动同意 AI 兜底,可能产生费用;可随时在插件市场页面最底部恢复提醒)",
|
|
175
|
+
aiRememberNote: "已按设置自动同意本地 AI 兜底(如需恢复弹窗提醒,请到市场底部修改)",
|
|
176
|
+
aiRememberReset: "已关闭 AI 兜底弹窗提醒(点击恢复提醒)",
|
|
177
|
+
consentWaiting: "等待授权确认…",
|
|
178
|
+
dismissFailure: "关闭此条提示",
|
|
179
|
+
sourcesBtn: "软件源",
|
|
180
|
+
sourcesTitle: "软件源管理",
|
|
181
|
+
sourcesDesc: "安装源(registry):安装时按主→备依次尝试;支持私有 / 内网 registry",
|
|
182
|
+
sourcePrimary: "主源",
|
|
183
|
+
setPrimary: "设为主源",
|
|
184
|
+
removeSource: "删除",
|
|
185
|
+
sourceName: "名称",
|
|
186
|
+
sourceUrl: "地址(https://…)",
|
|
187
|
+
addSource: "添加",
|
|
188
|
+
resetSources: "恢复默认",
|
|
189
|
+
closeModal: "关闭",
|
|
190
|
+
invalidSourceUrl: "软件源地址必须是 https:// 开头(或本机/私网 http://)的合法 URL",
|
|
191
|
+
editSource: "编辑",
|
|
192
|
+
saveSource: "保存",
|
|
193
|
+
cancelEdit: "取消",
|
|
194
|
+
searchSourcesTitle: "搜索源",
|
|
195
|
+
searchSourcesDesc: "搜索平台(点击登录标切换)。自定义源用 URL 模板:{q}=关键词、{page}=页码",
|
|
196
|
+
addSearchSource: "添加搜索源",
|
|
197
|
+
searchUrlPlaceholder: "URL 模板(必须含 {q})",
|
|
198
|
+
invalidSearchUrl: "搜索 URL 模板必须包含 {q} 占位符",
|
|
199
|
+
headersPlaceholder: "请求头(可选):每行一个,格式 名称: 值,如 Authorization: Bearer 令牌",
|
|
200
|
+
giteeTitle: "Gitee 登录(可选)",
|
|
201
|
+
giteeDesc: "Gitee 直装模式无需登录;登录仅用于提高接口限额(可选):① 打开 gitee.com → 设置 → 数据管理 → 第三方应用(或直接访问 https://gitee.com/oauth/applications/new);② 新建应用:应用名称随意,应用主页填 http://127.0.0.1:{port}(任意有效网址即可),应用回调地址填 http://127.0.0.1:{port}/plugin-console/gitee-oauth-callback,权限勾选 user_info、projects;③ 创建成功后把 Client ID / Client Secret 填到下面并点「保存配置」,再点「授权登录 Gitee」",
|
|
202
|
+
giteeClientId: "Client ID",
|
|
203
|
+
giteeClientSecret: "Client Secret",
|
|
204
|
+
giteeSave: "保存配置",
|
|
205
|
+
giteeLoginBtn: "授权登录 Gitee",
|
|
206
|
+
giteeLoggedIn: "已登录 Gitee:",
|
|
207
|
+
giteeClear: "清除登录",
|
|
208
|
+
giteeSetupHint: "未登录(可选)——直装模式无需登录,登录仅提高接口限额",
|
|
209
|
+
giteeLoginPrompt: "Gitee 源不需要登录:直接输入仓库名 owner/repo 搜索安装即可",
|
|
210
|
+
sourcesUpdated: "软件源已更新",
|
|
211
|
+
sourceOf: "源:",
|
|
212
|
+
searchSourceTitle: "点击切换搜索源(GitHub / Gitee / 自定义)",
|
|
213
|
+
recentFailures: "最近安装失败",
|
|
214
|
+
installedAt: "已安装",
|
|
215
|
+
checkUpdate: "检测更新",
|
|
216
|
+
checkingUpdate: "检测中…",
|
|
217
|
+
updateCheckFailed: "检测失败",
|
|
218
|
+
updateAvailable: "发现新版本",
|
|
219
|
+
upToDate: "已是最新版本",
|
|
220
|
+
updateNow: "更新",
|
|
221
|
+
depsOutdatedHint: "本包更新后还需同步以下子包版本(避免版本混搭导致启动冲突)",
|
|
222
|
+
update: "更新版本",
|
|
223
|
+
officialBadge: "官方",
|
|
224
|
+
aggregateBadge: "聚合",
|
|
225
|
+
officialTitle: "官方 dsh plugin add 通道可安装(dsh.bundle 清单)",
|
|
226
|
+
aggregateTitle: "聚合仓库:根包未发布,子包才是插件(查看详情可逐个安装)",
|
|
227
|
+
officialOnlyToggle: "只看官方",
|
|
228
|
+
officialChecking: "正在识别插件类型…",
|
|
229
|
+
elapsed: "已用时",
|
|
230
|
+
skillBadge: "技能",
|
|
231
|
+
skillTitle: "仓库含 SKILL.md,可按技能安装到 ~/.dsh/skills(由 dsh-skill-filesystem 扫描发现)",
|
|
232
|
+
skillTab: "技能",
|
|
233
|
+
pluginsTab: "插件",
|
|
234
|
+
skillMarketEmpty: "技能库为空(自动收录 agent-skills / claude-skills / dsh-skill topic 仓库)",
|
|
235
|
+
installSkill: "装技能",
|
|
236
|
+
skillInstalledTag: "已装",
|
|
237
|
+
skillInstalledMsg: "技能安装完成",
|
|
238
|
+
skillInstallNote: "技能已复制到 ~/.dsh/skills;当前 profile 若未启用 dsh-skill-filesystem 插件,技能不会被发现(启用后重启即可)",
|
|
239
|
+
skillUninstallHint: "删除技能:直接删除 ~/.dsh/skills/<名称> 目录",
|
|
240
|
+
modeBtn: "切换市场模式:插件市场 / 技能市场(技能从 SKILL.md 仓库一键安装到 ~/.dsh/skills)",
|
|
241
|
+
skillsMode: "技能",
|
|
242
|
+
pluginsMode: "插件",
|
|
243
|
+
skillsMarketTitle: "技能市场(GitHub)",
|
|
244
|
+
skillsMarketTitleOther: "技能市场({source})",
|
|
245
|
+
skillsSearchPlaceholder: "搜索技能仓库(agent-skills / claude-skills / dsh-skill topic;留空浏览全部)",
|
|
246
|
+
skillsNote: "技能模式仅支持 GitHub 源;技能安装 = 克隆 SKILL.md 仓库到 ~/.dsh/skills/<名称>/,不写补丁、无需重启",
|
|
247
|
+
skillsGithubOnly: "技能搜索仅支持 GitHub 源(其他源请在插件模式使用)",
|
|
248
|
+
installedSkillsTitle: "已安装技能",
|
|
249
|
+
skillDelete: "删除技能",
|
|
250
|
+
skillDeleteConfirm: "确认删除技能「{name}」?",
|
|
251
|
+
skillDeletedMsg: "技能已删除",
|
|
252
|
+
skillsEmpty: "技能库为空(自动收录 agent-skills / claude-skills / dsh-skill topic 仓库)",
|
|
253
|
+
skillSystemTag: "系统",
|
|
254
|
+
skillSystemTitle: "系统自带技能(~/.dsh/skills/.system 等保留目录),禁止删除",
|
|
255
|
+
skillDisable: "停用",
|
|
256
|
+
skillEnable: "启用",
|
|
257
|
+
skillDisabledTag: "已停用",
|
|
258
|
+
skillDisabledTitle: "已停用:模型与用户命令均不可调用(disable-model-invocation: true,可随时启用)",
|
|
259
|
+
skillToggledMsg: "技能已停用",
|
|
260
|
+
skillEnabledMsg: "技能已启用",
|
|
261
|
+
skillMetaLabel: "技能摘要",
|
|
262
|
+
skillNameLabel: "技能名",
|
|
263
|
+
skillWhenToUseLabel: "适用场景",
|
|
264
|
+
skillRepoNote: "技能仓库(SKILL.md),非 npm 包",
|
|
265
|
+
skillTopicsLabel: "来源",
|
|
266
|
+
skillDetailInstalled: "已装",
|
|
267
|
+
skillDetailDisabled: "已停用",
|
|
268
|
+
skillDetailPath: "安装位置",
|
|
269
|
+
suiteBadge: "套装",
|
|
270
|
+
suiteTitle: "submodule 聚合套装:自动装配子模块组件(bundle 插件 / 普通插件 / 技能 / agent 预设),照仓库 install.ps1 语义执行、不运行第三方脚本",
|
|
271
|
+
suiteInstall: "安装套装",
|
|
272
|
+
suiteInstalledMsg: "套装安装完成",
|
|
273
|
+
suiteComponentType: "组件",
|
|
274
|
+
suiteComponentOk: "成功",
|
|
275
|
+
suiteComponentFail: "失败",
|
|
276
|
+
suiteRepoNote: "套装仓库(submodule 聚合),非 npm 包——安装方式见下方官方命令",
|
|
277
|
+
officialInstallLabel: "官方安装方式",
|
|
278
|
+
copyInstallCmd: "复制",
|
|
279
|
+
copiedInstallCmd: "安装命令已复制,可粘贴到终端手动执行",
|
|
280
|
+
frameworkUpgradeNotice: "检测到 DSH 框架升级:{from} → {to}。已自动备份升级前配置到 {dir},框架补丁:{patch};第三方插件请留意兼容性。",
|
|
281
|
+
frameworkPatchApplied: "已重新应用",
|
|
282
|
+
frameworkPatchSkipped: "无需应用",
|
|
283
|
+
frameworkUpgradeBtn: "框架升级",
|
|
284
|
+
frameworkUpgradeTitle: "框架升级:自动打包备份现有配置 → 停止并重启 DSH 服务 → npm 升级框架与配套包(失败自动回滚)→ 拉起服务并适配",
|
|
285
|
+
frameworkUpgradeConfirm: "确认升级?(服务将自动重启)",
|
|
286
|
+
frameworkUpgradeDone: "框架升级流程已执行",
|
|
287
|
+
frameworkUpgradeUpToDate: "(已是最新版本)",
|
|
288
|
+
frameworkUpgradeCheckFailed: "(版本检测失败,无法确认是否有更新——请检查网络后重试)",
|
|
289
|
+
frameworkUpToDate: "已是最新框架",
|
|
290
|
+
frameworkUpToDateTitle: "当前 DSH 框架已是最新版本(升级请走官方流程或等新版本发布)",
|
|
291
|
+
frameworkUseUpgrade: "deepseek-harness 是 DSH 框架本体,无需安装;升级请使用卡片上的「框架升级」流程(有新版本时出现)",
|
|
292
|
+
relaunchBtn: "拉起服务",
|
|
293
|
+
relaunchTitle: "框架升级期间服务可能断开——点此手动拉起 DSH 服务(升级脚本也会自动拉起)",
|
|
294
|
+
relaunchDone: "已发起手动拉起(端口无监听时自动启动服务)",
|
|
295
|
+
fwStepBackup: "备份现有配置",
|
|
296
|
+
fwStepStop: "停止服务(页面将断开)",
|
|
297
|
+
fwStepInstall: "升级框架本体",
|
|
298
|
+
fwStepRollback: "升级失败回滚",
|
|
299
|
+
fwStepPkg: "更新官方配套包",
|
|
300
|
+
fwStepRelaunch: "拉起 DSH 服务",
|
|
301
|
+
fwStepDone: "升级完成",
|
|
302
|
+
installBusy: "已有安装/更新任务进行中,请等待完成后再试",
|
|
303
|
+
};
|
|
304
|
+
const en = {
|
|
305
|
+
tab: "Plugin console",
|
|
306
|
+
loading: "Reading plugins…",
|
|
307
|
+
error: "Plugins are temporarily unavailable.",
|
|
308
|
+
retry: "Retry",
|
|
309
|
+
installedTitle: "Installed plugins",
|
|
310
|
+
marketTitle: "Plugin market (GitHub)",
|
|
311
|
+
marketTitleOther: "Plugin market ({source})",
|
|
312
|
+
search: "Search",
|
|
313
|
+
searchPlaceholder: "Search dsh-plugin projects on GitHub (empty = default query)",
|
|
314
|
+
searchPlaceholderOther: "Search {source} for plugins (empty = default query)",
|
|
315
|
+
multiSourceTitle: "Multi-source: GitHub + all custom sources in parallel (Gitee direct mode excluded)",
|
|
316
|
+
multiSourceToggle: "Multi-source search toggle",
|
|
317
|
+
multiEmpty: "No matching plugins found in any search source.",
|
|
318
|
+
giteeRepoPlaceholder: "Enter a Gitee repo (owner/repo, e.g. oschina/git-osc)",
|
|
319
|
+
giteeEmpty: "Gitee's official search API is deprecated: type a repo name (owner/repo, e.g. oschina/git-osc) in the search box and press Enter",
|
|
320
|
+
empty: "No plugins are available.",
|
|
321
|
+
on: "Enable",
|
|
322
|
+
off: "Disable",
|
|
323
|
+
enabledTag: "Enabled",
|
|
324
|
+
disabledTag: "Disabled",
|
|
325
|
+
userDisabledTag: "Disabled by patch",
|
|
326
|
+
userForcedTag: "Forced on by patch",
|
|
327
|
+
protectedTag: "Protected",
|
|
328
|
+
unobserved: "Not mounted",
|
|
329
|
+
pending: "Waiting for dependencies",
|
|
330
|
+
loadingPhase: "Loading",
|
|
331
|
+
active: "Mounted",
|
|
332
|
+
failed: "Mount failed",
|
|
333
|
+
unloading: "Unloading",
|
|
334
|
+
toggledOn: "Enable requested",
|
|
335
|
+
toggledOff: "Disable requested",
|
|
336
|
+
failed: "Operation failed",
|
|
337
|
+
view: "Inspect",
|
|
338
|
+
install: "Add & enable",
|
|
339
|
+
installing: "Installing…",
|
|
340
|
+
marketLoading: "Searching GitHub…",
|
|
341
|
+
marketLoadingOther: "Searching {source}…",
|
|
342
|
+
directOther: "Searching {source} through the server channel (custom sources do not use browser-direct)",
|
|
343
|
+
marketError: "Search failed",
|
|
344
|
+
marketEmpty: "No matching projects.",
|
|
345
|
+
repoLoading: "Reading repository…",
|
|
346
|
+
repoError: "Failed to read repository",
|
|
347
|
+
packageName: "npm package",
|
|
348
|
+
dshHint: "Looks like a DSH plugin",
|
|
349
|
+
noPackage: "This repository has no package.json, cannot install",
|
|
350
|
+
privateRootHint: "Root package is not published to npm (private); cannot install directly",
|
|
351
|
+
subpackagesTitle: "Repository subpackages (installable individually)",
|
|
352
|
+
subpackagesLoading: "Reading subpackage list…",
|
|
353
|
+
subpackagesError: "Failed to read subpackages (retry later if the network is flaky)",
|
|
354
|
+
subpackagesEmpty: "No installable subpackages found.",
|
|
355
|
+
recentFailures: "Recent install failures",
|
|
356
|
+
installed: "Installed and enabled",
|
|
357
|
+
installNote: "HMR is applying; refresh the page if the plugin ships UI",
|
|
358
|
+
bundleNote: "This package is a bundle-layer plugin (e.g. skin packs); restart the DSH service to activate it",
|
|
359
|
+
direct: "(The browser calls GitHub directly; the server channel is used as fallback.)",
|
|
360
|
+
details: "Details",
|
|
361
|
+
expandDesc: "Expand",
|
|
362
|
+
collapseDesc: "Collapse",
|
|
363
|
+
addLocal: "Add locally",
|
|
364
|
+
installingLocal: "Installing…",
|
|
365
|
+
needPackage: "This repository has no package.json, cannot add directly",
|
|
366
|
+
noDetail: "No description provided for this plugin.",
|
|
367
|
+
versionLabel: "Version",
|
|
368
|
+
repoLabel: "Repository",
|
|
369
|
+
homepageLabel: "Homepage",
|
|
370
|
+
readmeLabel: "Readme summary",
|
|
371
|
+
loadingDetails: "Reading details…",
|
|
372
|
+
githubLoggedIn: "GitHub signed in: ",
|
|
373
|
+
githubCornerOut: "Not signed in to GitHub",
|
|
374
|
+
searchInstalledPlaceholder: "Search installed plugins (name or id)",
|
|
375
|
+
extraFilter: "Installed",
|
|
376
|
+
extraFilterAll: "All",
|
|
377
|
+
extraTag: "Third-party",
|
|
378
|
+
extraFilterTitle: "Toggle: installed (extra/third-party) / all",
|
|
379
|
+
extraEmpty: "No installed extra (non-bundled) plugins.",
|
|
380
|
+
noMatch: "No matching plugins.",
|
|
381
|
+
loadMore: "Load more",
|
|
382
|
+
loadingMore: "Loading…",
|
|
383
|
+
backToSearch: "Back to search",
|
|
384
|
+
collapseResults: "Collapse results",
|
|
385
|
+
expandResults: "Expand results",
|
|
386
|
+
reloadPage: "Reload page",
|
|
387
|
+
refreshList: "Refresh plugin list",
|
|
388
|
+
stageLabel: "Stage",
|
|
389
|
+
stagePreparing: "Preparing",
|
|
390
|
+
stageInstalling: "Downloading & installing (safe to leave this page)",
|
|
391
|
+
stageConfiguring: "Writing enable config",
|
|
392
|
+
stageRepairing: "Local AI is taking over the install…",
|
|
393
|
+
restartService: "Restart service",
|
|
394
|
+
restarting: "Service restarting, the page will recover shortly…",
|
|
395
|
+
autoReload: "Page will auto-reload…",
|
|
396
|
+
aiRepaired: "Local AI took over and finished the repair; refresh the page",
|
|
397
|
+
deletePlugin: "Delete plugin (remove config and uninstall package)",
|
|
398
|
+
confirmDelete: "Confirm delete?",
|
|
399
|
+
deleting: "Deleting…",
|
|
400
|
+
deleteNote: "Plugin deleted and package uninstalled; refreshing",
|
|
401
|
+
deleteBundleNote: "Owning bundle removed; takes effect after service restart",
|
|
402
|
+
aiConsentText: "All regular install channels failed. Next, the local AI takes over the install, which calls a DeepSeek API model and may incur API costs. Continue?",
|
|
403
|
+
aiConsentNotifyTitle: "Plugin install needs approval",
|
|
404
|
+
aiConsentApprove: "Agree, continue (may incur costs)",
|
|
405
|
+
aiConsentDecline: "Cancel",
|
|
406
|
+
aiFallbackLabel: "AI fallback",
|
|
407
|
+
aiFallbackTitle: "When off: if regular channels fail, the install is cancelled directly without calling the model API (zero cost)",
|
|
408
|
+
aiFallbackOff: "AI fallback is disabled by your setting; this install was cancelled (zero cost)",
|
|
409
|
+
aiConsentRemember: "Don't ask again (auto-approve the AI fallback, may incur costs; restore reminders anytime at the very bottom of the market)",
|
|
410
|
+
aiRememberNote: "AI fallback auto-approved per your setting (restore reminders at the market bottom)",
|
|
411
|
+
aiRememberReset: "AI fallback reminders disabled (click to restore)",
|
|
412
|
+
consentWaiting: "Awaiting approval…",
|
|
413
|
+
dismissFailure: "Dismiss this notice",
|
|
414
|
+
sourcesBtn: "Sources",
|
|
415
|
+
sourcesTitle: "Software sources",
|
|
416
|
+
sourcesDesc: "Install registries, tried primary-first; private / intranet registries supported",
|
|
417
|
+
sourcePrimary: "Primary",
|
|
418
|
+
setPrimary: "Set primary",
|
|
419
|
+
removeSource: "Remove",
|
|
420
|
+
sourceName: "Name",
|
|
421
|
+
sourceUrl: "URL (https://…)",
|
|
422
|
+
addSource: "Add",
|
|
423
|
+
resetSources: "Reset defaults",
|
|
424
|
+
closeModal: "Close",
|
|
425
|
+
invalidSourceUrl: "Source URL must start with https:// (or a private http:// address)",
|
|
426
|
+
editSource: "Edit",
|
|
427
|
+
saveSource: "Save",
|
|
428
|
+
cancelEdit: "Cancel",
|
|
429
|
+
searchSourcesTitle: "Search sources",
|
|
430
|
+
searchSourcesDesc: "Search platforms (switch via the pill). Custom sources use a URL template: {q}=keyword, {page}=page number",
|
|
431
|
+
addSearchSource: "Add search source",
|
|
432
|
+
searchUrlPlaceholder: "URL template (must contain {q})",
|
|
433
|
+
invalidSearchUrl: "Search URL template must contain the {q} placeholder",
|
|
434
|
+
headersPlaceholder: "Headers (optional): one per line, format Name: Value, e.g. Authorization: Bearer token",
|
|
435
|
+
giteeTitle: "Gitee login (optional)",
|
|
436
|
+
giteeDesc: "Gitee direct-repo mode needs no login; login only raises API rate limits (optional): ① Open gitee.com → Settings → Data management → Third-party apps (or go directly to https://gitee.com/oauth/applications/new); ② Create an app: any app name, homepage URL = http://127.0.0.1:{port} (any valid URL works), redirect/callback URL = http://127.0.0.1:{port}/plugin-console/gitee-oauth-callback, grant scope user_info, projects; ③ Fill Client ID / Client Secret below, click Save, then click Authorize Gitee",
|
|
437
|
+
giteeClientId: "Client ID",
|
|
438
|
+
giteeClientSecret: "Client Secret",
|
|
439
|
+
giteeSave: "Save",
|
|
440
|
+
giteeLoginBtn: "Authorize Gitee",
|
|
441
|
+
giteeLoggedIn: "Signed in to Gitee: ",
|
|
442
|
+
giteeClear: "Clear login",
|
|
443
|
+
giteeSetupHint: "Not signed in (optional) — direct mode needs no login; login raises rate limits",
|
|
444
|
+
giteeLoginPrompt: "Gitee login is optional: type a repo name owner/repo to search and install",
|
|
445
|
+
sourcesUpdated: "Sources updated",
|
|
446
|
+
sourceOf: "Source: ",
|
|
447
|
+
searchSourceTitle: "Click to switch search source (GitHub / Gitee / custom)",
|
|
448
|
+
recentFailures: "Recent install failures",
|
|
449
|
+
installedAt: "Installed",
|
|
450
|
+
checkUpdate: "Check update",
|
|
451
|
+
checkingUpdate: "Checking…",
|
|
452
|
+
updateCheckFailed: "Check failed",
|
|
453
|
+
updateAvailable: "New version available",
|
|
454
|
+
upToDate: "Up to date",
|
|
455
|
+
updateNow: "Update",
|
|
456
|
+
depsOutdatedHint: "Updating this package also needs these subpackage versions (avoid mixed-version startup conflicts)",
|
|
457
|
+
update: "Update",
|
|
458
|
+
officialBadge: "Official",
|
|
459
|
+
aggregateBadge: "Aggregate",
|
|
460
|
+
officialTitle: "Installable via official dsh plugin add (dsh.bundle manifest)",
|
|
461
|
+
aggregateTitle: "Aggregate repo: root unpublished; subpackages are the plugins (view to install)",
|
|
462
|
+
officialOnlyToggle: "Official only",
|
|
463
|
+
officialChecking: "Identifying plugin types…",
|
|
464
|
+
elapsed: "Elapsed",
|
|
465
|
+
skillBadge: "Skill",
|
|
466
|
+
skillTitle: "Repo contains SKILL.md — installable as a skill into ~/.dsh/skills (scanned by dsh-skill-filesystem)",
|
|
467
|
+
skillTab: "Skills",
|
|
468
|
+
pluginsTab: "Plugins",
|
|
469
|
+
skillMarketEmpty: "Skill library is empty (auto-collected from agent-skills / claude-skills / dsh-skill topic repos)",
|
|
470
|
+
installSkill: "Install skill",
|
|
471
|
+
skillInstalledTag: "Installed",
|
|
472
|
+
skillInstalledMsg: "Skill installed",
|
|
473
|
+
skillInstallNote: "Skill copied to ~/.dsh/skills; if the current profile does not enable the dsh-skill-filesystem plugin the skill won't be discovered (enable it and restart)",
|
|
474
|
+
skillUninstallHint: "To remove a skill: delete the ~/.dsh/skills/<name> directory",
|
|
475
|
+
modeBtn: "Switch market mode: plugin market / skill market (skills install from SKILL.md repos into ~/.dsh/skills)",
|
|
476
|
+
skillsMode: "Skills",
|
|
477
|
+
pluginsMode: "Plugins",
|
|
478
|
+
skillsMarketTitle: "Skill market (GitHub)",
|
|
479
|
+
skillsMarketTitleOther: "Skill market ({source})",
|
|
480
|
+
skillsSearchPlaceholder: "Search skill repos (agent-skills / claude-skills / dsh-skill topics; empty = browse all)",
|
|
481
|
+
skillsNote: "Skill mode supports the GitHub source only; installing a skill = clone the SKILL.md repo into ~/.dsh/skills/<name>/, no patch, no restart",
|
|
482
|
+
skillsGithubOnly: "Skill search supports the GitHub source only (use plugin mode for other sources)",
|
|
483
|
+
installedSkillsTitle: "Installed skills",
|
|
484
|
+
skillDelete: "Remove skill",
|
|
485
|
+
skillDeleteConfirm: "Remove skill「{name}」?",
|
|
486
|
+
skillDeletedMsg: "Skill removed",
|
|
487
|
+
skillsEmpty: "Skill library is empty (auto-collected from agent-skills / claude-skills / dsh-skill topic repos)",
|
|
488
|
+
skillSystemTag: "System",
|
|
489
|
+
skillSystemTitle: "System-owned skill (~/.dsh/skills/.system etc.), cannot be removed",
|
|
490
|
+
skillDisable: "Disable",
|
|
491
|
+
skillEnable: "Enable",
|
|
492
|
+
skillDisabledTag: "Disabled",
|
|
493
|
+
skillDisabledTitle: "Disabled: neither model nor user commands can invoke it (disable-model-invocation: true, re-enable anytime)",
|
|
494
|
+
skillToggledMsg: "Skill disabled",
|
|
495
|
+
skillEnabledMsg: "Skill enabled",
|
|
496
|
+
skillMetaLabel: "Skill summary",
|
|
497
|
+
skillNameLabel: "Skill name",
|
|
498
|
+
skillWhenToUseLabel: "When to use",
|
|
499
|
+
skillRepoNote: "Skill repo (SKILL.md), not an npm package",
|
|
500
|
+
skillTopicsLabel: "Source",
|
|
501
|
+
skillDetailInstalled: "Installed",
|
|
502
|
+
skillDetailDisabled: "Disabled",
|
|
503
|
+
skillDetailPath: "Location",
|
|
504
|
+
suiteBadge: "Suite",
|
|
505
|
+
suiteTitle: "Submodule aggregate suite: auto-assembles components (bundle plugins / plugins / skills / agent presets) following the repo's install.ps1 semantics without executing third-party scripts",
|
|
506
|
+
suiteInstall: "Install suite",
|
|
507
|
+
suiteInstalledMsg: "Suite installed",
|
|
508
|
+
suiteComponentType: "Component",
|
|
509
|
+
suiteComponentOk: "OK",
|
|
510
|
+
suiteComponentFail: "Failed",
|
|
511
|
+
suiteRepoNote: "Suite repo (submodule aggregate), not an npm package — see the official install command below",
|
|
512
|
+
officialInstallLabel: "Official install",
|
|
513
|
+
copyInstallCmd: "Copy",
|
|
514
|
+
copiedInstallCmd: "Install command copied — paste into your terminal to run it manually",
|
|
515
|
+
frameworkUpgradeNotice: "DSH framework upgrade detected: {from} → {to}. Pre-upgrade config backed up to {dir}; framework patch: {patch}. Check third-party plugin compatibility.",
|
|
516
|
+
frameworkPatchApplied: "reapplied",
|
|
517
|
+
frameworkPatchSkipped: "not needed",
|
|
518
|
+
frameworkUpgradeBtn: "Framework upgrade",
|
|
519
|
+
frameworkUpgradeTitle: "Framework upgrade: backup config → stop & restart DSH service → npm upgrade framework + official packages (auto rollback on failure) → relaunch and adapt",
|
|
520
|
+
frameworkUpgradeConfirm: "Confirm upgrade? (service will restart)",
|
|
521
|
+
frameworkUpgradeDone: "Framework upgrade flow executed",
|
|
522
|
+
frameworkUpgradeUpToDate: " (already latest)",
|
|
523
|
+
frameworkUpgradeCheckFailed: " (version check failed - cannot confirm update, check network and retry)",
|
|
524
|
+
frameworkUpToDate: "Framework up to date",
|
|
525
|
+
frameworkUpToDateTitle: "Current DSH framework is already the latest (upgrade via the official flow or wait for a new release)",
|
|
526
|
+
frameworkUseUpgrade: "deepseek-harness is the DSH framework itself, not a plugin; upgrade via the card's Framework Upgrade flow (shown when a new version exists)",
|
|
527
|
+
relaunchBtn: "Relaunch service",
|
|
528
|
+
relaunchTitle: "During a framework upgrade the service may disconnect — click to manually relaunch the DSH service (the upgrade script also relaunches automatically)",
|
|
529
|
+
relaunchDone: "Manual relaunch requested (starts the service if the port is not listening)",
|
|
530
|
+
fwStepBackup: "Backup current config",
|
|
531
|
+
fwStepStop: "Stop service (page will disconnect)",
|
|
532
|
+
fwStepInstall: "Upgrade framework",
|
|
533
|
+
fwStepRollback: "Rollback on failure",
|
|
534
|
+
fwStepPkg: "Update official packages",
|
|
535
|
+
fwStepRelaunch: "Relaunch DSH service",
|
|
536
|
+
fwStepDone: "Upgrade complete",
|
|
537
|
+
};
|
|
538
|
+
//#endregion
|
|
539
|
+
const NS = "settings.pluginConsole";
|
|
540
|
+
const inject = ["slots", "locale"];
|
|
541
|
+
const PHASE_KEYS = { pending: "pending", loading: "loadingPhase", active: "active", failed: "failed", unloading: "unloading" };
|
|
542
|
+
const GITHUB_API = "https://api.github.com";
|
|
543
|
+
const GITHUB_RAW = "https://raw.githubusercontent.com";
|
|
544
|
+
const el = react.createElement;
|
|
545
|
+
function moduleShortName(moduleName) {
|
|
546
|
+
return (moduleName.startsWith("@") ? moduleName.slice(moduleName.indexOf("/") + 1) : moduleName)
|
|
547
|
+
.replace(/^cordis:/, "").replace(/^cordis-plugin-/, "").replace(/^dsh-(?:host-|client-)?/, "");
|
|
548
|
+
}
|
|
549
|
+
/** 提取 README 标题与开篇摘要(与宿主端 summarizeReadme 同规则)。 */
|
|
550
|
+
function summarizeReadmeText(text) {
|
|
551
|
+
const lines = text.split(/\r?\n/u);
|
|
552
|
+
let title = "";
|
|
553
|
+
const intro = [];
|
|
554
|
+
for (const line of lines) {
|
|
555
|
+
const heading = line.match(/^(#{1,3})\s+(.+)$/u);
|
|
556
|
+
if (heading) {
|
|
557
|
+
if (title === "") { title = heading[2].trim(); continue; }
|
|
558
|
+
break;
|
|
559
|
+
}
|
|
560
|
+
if (title === "") continue;
|
|
561
|
+
const cleaned = line.replace(/!\[[^\]]*\]\([^)]*\)/gu, "")
|
|
562
|
+
.replace(/\[([^\]]+)\]\([^)]*\)/gu, "$1")
|
|
563
|
+
.replace(/[`*_~]/gu, "")
|
|
564
|
+
.trim();
|
|
565
|
+
if (cleaned) intro.push(cleaned);
|
|
566
|
+
if (intro.join(" ").length > 700) break;
|
|
567
|
+
}
|
|
568
|
+
return { title, summary: intro.join(" ").trim().slice(0, 900) };
|
|
569
|
+
}
|
|
570
|
+
function phaseLabel(phase, t) {
|
|
571
|
+
return phase === null ? t("unobserved") : t(PHASE_KEYS[phase]);
|
|
572
|
+
}
|
|
573
|
+
async function call(path, body) {
|
|
574
|
+
const response = await fetch(path, body === undefined
|
|
575
|
+
? {}
|
|
576
|
+
: { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
577
|
+
let data = null;
|
|
578
|
+
try {
|
|
579
|
+
data = await response.json();
|
|
580
|
+
} catch {}
|
|
581
|
+
if (!response.ok || (data !== null && data.ok === false)) {
|
|
582
|
+
throw new Error(data !== null && typeof data.error === "string" ? data.error : "HTTP " + response.status);
|
|
583
|
+
}
|
|
584
|
+
return data;
|
|
585
|
+
}
|
|
586
|
+
/** 超时类错误附上可操作提示(网络黑洞期的最常见表现)。 */
|
|
587
|
+
function friendlyGithubError(error) {
|
|
588
|
+
const msg = error !== null && typeof error.message === "string" ? error.message : String(error);
|
|
589
|
+
if (/超时|timeout|aborted|网络/iu.test(msg)) {
|
|
590
|
+
return new Error(msg + "(当前网络可能处于波动期:稍等一两分钟再试;或刷新页面,让浏览器直连通道重新工作)");
|
|
591
|
+
}
|
|
592
|
+
return error;
|
|
593
|
+
}
|
|
594
|
+
async function githubFetch(url) {
|
|
595
|
+
const response = await fetch(url, {
|
|
596
|
+
headers: { accept: "application/vnd.github+json" },
|
|
597
|
+
signal: AbortSignal.timeout(15000),
|
|
598
|
+
});
|
|
599
|
+
if (response.status === 403) throw new Error("GitHub 匿名接口限流已用尽,请稍后再试");
|
|
600
|
+
if (!response.ok) throw new Error("GitHub 请求失败 (HTTP " + response.status + ")");
|
|
601
|
+
return response.json();
|
|
602
|
+
}
|
|
603
|
+
/** raw 文件多通道竞速:官方直连 + 常见 gh 镜像,谁先成功用谁;非 GitHub 平台走对应 raw 地址。 */
|
|
604
|
+
const RAW_CANDIDATES = [
|
|
605
|
+
(repo, branch, file) => `${GITHUB_RAW}/${repo}/${branch}/${file}`,
|
|
606
|
+
(repo, branch, file) => `https://ghproxy.net/https://raw.githubusercontent.com/${repo}/${branch}/${file}`,
|
|
607
|
+
(repo, branch, file) => `https://ghfast.top/https://raw.githubusercontent.com/${repo}/${branch}/${file}`,
|
|
608
|
+
(repo, branch, file) => `https://mirror.ghproxy.com/https://raw.githubusercontent.com/${repo}/${branch}/${file}`,
|
|
609
|
+
];
|
|
610
|
+
async function fetchRawText(repo, branch, file, source = "github") {
|
|
611
|
+
if (source === "gitee") {
|
|
612
|
+
try {
|
|
613
|
+
const res = await fetch(`https://gitee.com/${repo}/raw/${branch}/${file}`, { signal: AbortSignal.timeout(15000) });
|
|
614
|
+
if (!res.ok) return null;
|
|
615
|
+
return await res.text();
|
|
616
|
+
} catch { return null; }
|
|
617
|
+
}
|
|
618
|
+
const attempts = RAW_CANDIDATES.map((build) => fetch(build(repo, branch, file), {
|
|
619
|
+
signal: AbortSignal.timeout(15000),
|
|
620
|
+
}).then((res) => {
|
|
621
|
+
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
622
|
+
return res.text();
|
|
623
|
+
}));
|
|
624
|
+
try {
|
|
625
|
+
return await Promise.any(attempts);
|
|
626
|
+
} catch {
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
/** 浏览器直连 GitHub 搜索;失败抛错由调用方回退到服务端通道。 */
|
|
631
|
+
async function searchFromGithub(q, page) {
|
|
632
|
+
const query = (q ?? "").trim() || "dsh-plugin";
|
|
633
|
+
const data = await githubFetch(`${GITHUB_API}/search/repositories?q=${encodeURIComponent(query)}&sort=stars&order=desc&per_page=20&page=${Math.max(page || 1, 1)}`);
|
|
634
|
+
return (data.items ?? []).map((item) => ({
|
|
635
|
+
fullName: item.full_name,
|
|
636
|
+
description: item.description ?? "",
|
|
637
|
+
htmlUrl: item.html_url,
|
|
638
|
+
stars: item.stargazers_count ?? 0,
|
|
639
|
+
updatedAt: item.updated_at ?? "",
|
|
640
|
+
defaultBranch: item.default_branch ?? "main",
|
|
641
|
+
topics: item.topics ?? [],
|
|
642
|
+
}));
|
|
643
|
+
}
|
|
644
|
+
/** 提取 SKILL.md frontmatter 摘要(name / description / whenToUse,与宿主端同规则)。 */
|
|
645
|
+
function summarizeSkillFrontmatter(text) {
|
|
646
|
+
const fm = text.startsWith("---") ? text.slice(3, text.indexOf("\n---", 3)) : "";
|
|
647
|
+
if (fm === "") return null;
|
|
648
|
+
const pick = (key) => {
|
|
649
|
+
const m = fm.match(new RegExp("^" + key + ":\\s*(.*)$", "mu"));
|
|
650
|
+
if (!m) return "";
|
|
651
|
+
const first = m[1].trim();
|
|
652
|
+
if (first.startsWith("|")) {
|
|
653
|
+
// 多行块:取后续行直到下一个键或结束
|
|
654
|
+
const rest = fm.slice(m.index + m[0].length);
|
|
655
|
+
const lines = [];
|
|
656
|
+
for (const line of rest.split("\n")) {
|
|
657
|
+
if (/^[a-zA-Z][\w-]*\s*:/u.test(line)) break;
|
|
658
|
+
const v = line.trim();
|
|
659
|
+
if (v) lines.push(v);
|
|
660
|
+
if (lines.join(" ").length > 240) break;
|
|
661
|
+
}
|
|
662
|
+
return lines.join(" ").slice(0, 500);
|
|
663
|
+
}
|
|
664
|
+
return first.slice(0, 200);
|
|
665
|
+
};
|
|
666
|
+
const name = pick("name");
|
|
667
|
+
const description = pick("description");
|
|
668
|
+
const whenToUse = pick("whenToUse");
|
|
669
|
+
if (!name && !description && !whenToUse) return null;
|
|
670
|
+
return { name, description, whenToUse };
|
|
671
|
+
}
|
|
672
|
+
/** 浏览器直连读取仓库元数据 + package.json + README 摘要。
|
|
673
|
+
* 搜索结果自带描述/star/分支信息,seed 存在时跳过 api.github.com 请求
|
|
674
|
+
* (该接口在黑洞期会挂起很久,是"查看没反应"的根因)。 */
|
|
675
|
+
async function repoInfoFromGithub(repo, seed) {
|
|
676
|
+
let branch = "main";
|
|
677
|
+
let metaDescription = "";
|
|
678
|
+
let metaStars = 0;
|
|
679
|
+
if (seed && typeof seed.defaultBranch === "string" && seed.defaultBranch) {
|
|
680
|
+
branch = seed.defaultBranch;
|
|
681
|
+
metaDescription = seed.description ?? "";
|
|
682
|
+
metaStars = seed.stars ?? 0;
|
|
683
|
+
} else {
|
|
684
|
+
const meta = await githubFetch(`${GITHUB_API}/repos/${encodeURIComponent(repo)}`);
|
|
685
|
+
branch = meta.default_branch ?? "main";
|
|
686
|
+
metaDescription = meta.description ?? "";
|
|
687
|
+
metaStars = meta.stargazers_count ?? 0;
|
|
688
|
+
}
|
|
689
|
+
let pkg = null;
|
|
690
|
+
let readme = null;
|
|
691
|
+
let skill = null;
|
|
692
|
+
const pkgText = await fetchRawText(repo, branch, "package.json");
|
|
693
|
+
if (pkgText !== null) {
|
|
694
|
+
try { pkg = JSON.parse(pkgText); } catch {}
|
|
695
|
+
}
|
|
696
|
+
const readmeText = await fetchRawText(repo, branch, "README.md");
|
|
697
|
+
if (readmeText !== null) readme = summarizeReadmeText(readmeText);
|
|
698
|
+
const skillText = await fetchRawText(repo, branch, "SKILL.md");
|
|
699
|
+
if (skillText !== null) skill = summarizeSkillFrontmatter(skillText);
|
|
700
|
+
return {
|
|
701
|
+
repo,
|
|
702
|
+
defaultBranch: branch,
|
|
703
|
+
description: metaDescription,
|
|
704
|
+
stars: metaStars,
|
|
705
|
+
packageName: pkg !== null && typeof pkg.name === "string" ? pkg.name : null,
|
|
706
|
+
packageDescription: pkg !== null && typeof pkg.description === "string" ? pkg.description : null,
|
|
707
|
+
hasPackageJson: pkg !== null,
|
|
708
|
+
privateRoot: pkg !== null && pkg.private === true,
|
|
709
|
+
readme,
|
|
710
|
+
skill,
|
|
711
|
+
dshHint: pkg !== null && (
|
|
712
|
+
typeof pkg.name === "string" && /(^|-)dsh[-/]/u.test(pkg.name)
|
|
713
|
+
|| pkg.peerDependencies !== undefined && pkg.peerDependencies["@deepseek-ai/cordis"] !== undefined
|
|
714
|
+
|| Array.isArray(pkg.keywords) && pkg.keywords.includes("dsh-plugin")
|
|
715
|
+
),
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
/** 只取仓库的 npm 包名与 private 标记(跳过 README 与 api.github.com,供"添加到本地"快捷路径使用)。 */
|
|
719
|
+
async function fetchPackageName(repo, seedBranch, source = "github") {
|
|
720
|
+
const branch = typeof seedBranch === "string" && seedBranch ? seedBranch : "main";
|
|
721
|
+
const pkgText = await fetchRawText(repo, branch, "package.json", source);
|
|
722
|
+
if (pkgText !== null) {
|
|
723
|
+
try {
|
|
724
|
+
const pkg = JSON.parse(pkgText);
|
|
725
|
+
if (pkg && typeof pkg.name === "string") return { name: pkg.name, private: pkg.private === true };
|
|
726
|
+
} catch {}
|
|
727
|
+
}
|
|
728
|
+
return null;
|
|
729
|
+
}
|
|
730
|
+
/** 非 GitHub 平台仓库详情:seed 自带元数据,直接用平台 raw 读 package.json 与 README。 */
|
|
731
|
+
async function repoInfoFromPlatform(item) {
|
|
732
|
+
const source = item.source ?? "github";
|
|
733
|
+
const branch = item.defaultBranch ?? (source === "gitee" ? "master" : "main");
|
|
734
|
+
let pkg = null;
|
|
735
|
+
let readme = null;
|
|
736
|
+
let skill = null;
|
|
737
|
+
const pkgText = await fetchRawText(item.fullName, branch, "package.json", source);
|
|
738
|
+
if (pkgText !== null) {
|
|
739
|
+
try { pkg = JSON.parse(pkgText); } catch {}
|
|
740
|
+
}
|
|
741
|
+
const readmeText = await fetchRawText(item.fullName, branch, "README.md", source);
|
|
742
|
+
if (readmeText !== null) readme = summarizeReadmeText(readmeText);
|
|
743
|
+
const skillText = await fetchRawText(item.fullName, branch, "SKILL.md", source);
|
|
744
|
+
if (skillText !== null) skill = summarizeSkillFrontmatter(skillText);
|
|
745
|
+
return {
|
|
746
|
+
repo: item.fullName,
|
|
747
|
+
defaultBranch: branch,
|
|
748
|
+
description: item.description ?? "",
|
|
749
|
+
stars: item.stars ?? 0,
|
|
750
|
+
packageName: pkg !== null && typeof pkg.name === "string" ? pkg.name : null,
|
|
751
|
+
packageDescription: pkg !== null && typeof pkg.description === "string" ? pkg.description : null,
|
|
752
|
+
hasPackageJson: pkg !== null,
|
|
753
|
+
privateRoot: pkg !== null && pkg.private === true,
|
|
754
|
+
readme,
|
|
755
|
+
skill,
|
|
756
|
+
dshHint: pkg !== null && (
|
|
757
|
+
typeof pkg.name === "string" && /(^|-)dsh[-/]/u.test(pkg.name)
|
|
758
|
+
|| pkg.peerDependencies !== undefined && pkg.peerDependencies["@deepseek-ai/cordis"] !== undefined
|
|
759
|
+
|| Array.isArray(pkg.keywords) && pkg.keywords.includes("dsh-plugin")
|
|
760
|
+
),
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
/** monorepo 子包列表:git trees 递归列出 packages 目录下的 package.json,逐个取 npm 包名(上限 24 个)。 */
|
|
764
|
+
async function fetchSubpackages(repo, branch) {
|
|
765
|
+
const data = await githubFetch(`https://api.github.com/repos/${encodeURIComponent(repo)}/git/trees/${encodeURIComponent(branch)}?recursive=1`);
|
|
766
|
+
const paths = (data.tree ?? []).filter((node) => node.type === "blob" && /^packages\/[^/]+\/package\.json$/u.test(node.path)).map((node) => node.path);
|
|
767
|
+
if (paths.length === 0) return [];
|
|
768
|
+
const out = [];
|
|
769
|
+
for (const path of paths.slice(0, 24)) {
|
|
770
|
+
const text = await fetchRawText(repo, branch, path);
|
|
771
|
+
if (text === null) continue;
|
|
772
|
+
try {
|
|
773
|
+
const pkg = JSON.parse(text);
|
|
774
|
+
if (pkg && typeof pkg.name === "string") out.push({ dir: path.split("/")[1], name: pkg.name });
|
|
775
|
+
} catch {}
|
|
776
|
+
}
|
|
777
|
+
return out;
|
|
778
|
+
}
|
|
779
|
+
/** 批量识别搜索结果:官方通道(根包 dsh.bundle)/ 聚合仓库(private+workspaces,子包才是插件)/ 普通项目。 */
|
|
780
|
+
async function enrichOfficialBundle(items) {
|
|
781
|
+
return Promise.all(items.map(async (item) => {
|
|
782
|
+
let official = null;
|
|
783
|
+
let aggregate = false;
|
|
784
|
+
const pkgText = await fetchRawText(item.fullName, item.defaultBranch, "package.json", item.source ?? "github");
|
|
785
|
+
if (pkgText !== null) {
|
|
786
|
+
try {
|
|
787
|
+
const pkg = JSON.parse(pkgText);
|
|
788
|
+
if (typeof pkg.dsh?.bundle?.patch === "string") {
|
|
789
|
+
official = true;
|
|
790
|
+
} else if (pkg.private === true && (Array.isArray(pkg.workspaces) || /(^|-)dsh[-/]/u.test(String(pkg.name ?? "")))) {
|
|
791
|
+
aggregate = true;
|
|
792
|
+
}
|
|
793
|
+
} catch {}
|
|
794
|
+
}
|
|
795
|
+
return { ...item, official, aggregate };
|
|
796
|
+
}));
|
|
797
|
+
}
|
|
798
|
+
/** 源地址校验(与宿主端一致):https 任意;http 仅限本机/私网(内网源常用 http)。 */
|
|
799
|
+
function isAllowedSourceUrl(url) {
|
|
800
|
+
if (/^https:\/\/\S+$/u.test(url)) return true;
|
|
801
|
+
if (!/^http:\/\/\S+$/u.test(url)) return false;
|
|
802
|
+
try {
|
|
803
|
+
const host = new URL(url).hostname.toLowerCase();
|
|
804
|
+
if (host === "localhost" || host === "::1" || host === "[::1]") return true;
|
|
805
|
+
if (/^127\.\d+\.\d+\.\d+$/u.test(host)) return true;
|
|
806
|
+
if (/^10\.\d+\.\d+\.\d+$/u.test(host)) return true;
|
|
807
|
+
if (/^192\.168\.\d+\.\d+$/u.test(host)) return true;
|
|
808
|
+
if (/^169\.254\.\d+\.\d+$/u.test(host)) return true;
|
|
809
|
+
const m = host.match(/^172\.(\d+)\.\d+\.\d+$/u);
|
|
810
|
+
if (m && Number(m[1]) >= 16 && Number(m[1]) <= 31) return true;
|
|
811
|
+
if (/^[0-9a-f]{1,4}(?::[0-9a-f]{1,4}){2,7}$/iu.test(host)) return true;
|
|
812
|
+
return false;
|
|
813
|
+
} catch {
|
|
814
|
+
return false;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
function PluginConsoleTab({ t }) {
|
|
818
|
+
const [state, setState] = react.useState({ status: "loading" });
|
|
819
|
+
const [busy, setBusy] = react.useState(null);
|
|
820
|
+
const [message, setMessage] = react.useState(null);
|
|
821
|
+
const [reloadHint, setReloadHint] = react.useState(false);
|
|
822
|
+
const [restartHint, setRestartHint] = react.useState(false);
|
|
823
|
+
const [jobs, setJobs] = react.useState({});
|
|
824
|
+
const [query, setQuery] = react.useState("");
|
|
825
|
+
const [market, setMarket] = react.useState(null);
|
|
826
|
+
const [marketPage, setMarketPage] = react.useState(1);
|
|
827
|
+
const [loadingMore, setLoadingMore] = react.useState(false);
|
|
828
|
+
const [repoInfo, setRepoInfo] = react.useState(null);
|
|
829
|
+
const [subpackages, setSubpackages] = react.useState(null);
|
|
830
|
+
const [installing, setInstalling] = react.useState(null);
|
|
831
|
+
const [details, setDetails] = react.useState(null);
|
|
832
|
+
// 已安装插件"检测更新":null=未检测 / {status:'checking'} / {status:'ready', latest, error}
|
|
833
|
+
const [updateCheck, setUpdateCheck] = react.useState(null);
|
|
834
|
+
// 静态插件索引(dsh-plugin topic 全量浏览):null=未加载 / {items, skills, loaded}
|
|
835
|
+
const [indexData, setIndexData] = react.useState(null);
|
|
836
|
+
// 索引浏览当前展示条数(分页展示,避免一次渲染 500 条)
|
|
837
|
+
const [indexShown, setIndexShown] = react.useState(50);
|
|
838
|
+
// 市场总模式:'plugins' = 插件市场 / 'skills' = 技能市场(localStorage 持久化)
|
|
839
|
+
const [mode, setMode] = react.useState(() => {
|
|
840
|
+
try { return localStorage.getItem("pc-market-mode") === "skills" ? "skills" : "plugins"; } catch { return "plugins"; }
|
|
841
|
+
});
|
|
842
|
+
// 已安装技能清单(~/.dsh/skills),[{name, dir|file}],用于技能条目「已装」标记与已安装区展示
|
|
843
|
+
const [installedSkills, setInstalledSkills] = react.useState([]);
|
|
844
|
+
// 原始页面标题(AI 授权标题闪烁后恢复用)
|
|
845
|
+
const originalTitleRef = react.useRef("");
|
|
846
|
+
// 技能删除两步确认:存待确认技能名
|
|
847
|
+
const [confirmSkillDelete, setConfirmSkillDelete] = react.useState(null);
|
|
848
|
+
// 技能停用/启用进行中
|
|
849
|
+
const [togglingSkill, setTogglingSkill] = react.useState(null);
|
|
850
|
+
// 套装安装报告:{ jobId: [{component, type, ok, note}] }
|
|
851
|
+
const [suiteReports, setSuiteReports] = react.useState({});
|
|
852
|
+
// 框架升级提示已关闭(本次会话内)
|
|
853
|
+
const [frameworkNoticeDismissed, setFrameworkNoticeDismissed] = react.useState(false);
|
|
854
|
+
// 进度条已关闭
|
|
855
|
+
const [fwStatusDismissed, setFwStatusDismissed] = react.useState(false);
|
|
856
|
+
// 框架升级:两步确认 + 执行中
|
|
857
|
+
const [confirmFrameworkUpgrade, setConfirmFrameworkUpgrade] = react.useState(false);
|
|
858
|
+
const [frameworkUpgrading, setFrameworkUpgrading] = react.useState(false);
|
|
859
|
+
// 框架升级进度(重连后恢复显示):{status, message}
|
|
860
|
+
const [frameworkStatus, setFrameworkStatus] = react.useState(null);
|
|
861
|
+
// 进度条步骤(与 host 脚本 SetState 的阶段对应)
|
|
862
|
+
const FW_STEPS = [
|
|
863
|
+
{ key: "starting", label: t("fwStepBackup") },
|
|
864
|
+
{ key: "stopped", label: t("fwStepStop") },
|
|
865
|
+
{ key: "installing", label: t("fwStepInstall") },
|
|
866
|
+
{ key: "rollback", label: t("fwStepRollback") },
|
|
867
|
+
{ key: "pkg", label: t("fwStepPkg") },
|
|
868
|
+
{ key: "relaunching", label: t("fwStepRelaunch") },
|
|
869
|
+
{ key: "done", label: t("fwStepDone") },
|
|
870
|
+
];
|
|
871
|
+
// 升级进度轮询:页面断开后请求失败自动停止;重连后由挂载恢复逻辑继续
|
|
872
|
+
const fwStatusTimerRef = react.useRef(null);
|
|
873
|
+
const pollFrameworkStatus = () => {
|
|
874
|
+
if (fwStatusTimerRef.current) return;
|
|
875
|
+
fwStatusTimerRef.current = window.setInterval(() => {
|
|
876
|
+
call("/plugin-console/framework-upgrade-status").then(
|
|
877
|
+
(data) => {
|
|
878
|
+
if (data && typeof data.status === "string") {
|
|
879
|
+
setFrameworkStatus(data);
|
|
880
|
+
if (data.status === "done" || data.status === "failed") {
|
|
881
|
+
window.clearInterval(fwStatusTimerRef.current);
|
|
882
|
+
fwStatusTimerRef.current = null;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
},
|
|
886
|
+
() => {}, // 服务断开:轮询失败静默(重连后由挂载逻辑恢复)
|
|
887
|
+
);
|
|
888
|
+
}, 3000);
|
|
889
|
+
};
|
|
890
|
+
// 挂载/重连后恢复升级进度(若上次升级仍在进行/刚完成)
|
|
891
|
+
react.useEffect(() => {
|
|
892
|
+
call("/plugin-console/framework-upgrade-status").then(
|
|
893
|
+
(data) => {
|
|
894
|
+
if (data && typeof data.status === "string" && data.status !== "idle") {
|
|
895
|
+
// 终态(done/failed)且用户此前点过叉号关闭:永久不再恢复显示(localStorage 持久化)
|
|
896
|
+
let dismissedTerminal = null;
|
|
897
|
+
try { dismissedTerminal = localStorage.getItem("pc-fw-dismiss-terminal"); } catch {}
|
|
898
|
+
if (data.status !== dismissedTerminal) {
|
|
899
|
+
setFrameworkStatus(data);
|
|
900
|
+
if (data.status !== "done" && data.status !== "failed") pollFrameworkStatus();
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
},
|
|
904
|
+
() => {},
|
|
905
|
+
);
|
|
906
|
+
return () => { if (fwStatusTimerRef.current) { window.clearInterval(fwStatusTimerRef.current); fwStatusTimerRef.current = null; } };
|
|
907
|
+
}, []);
|
|
908
|
+
// 自动版本比对结果:{ fullName: latest }(已安装且有新版)
|
|
909
|
+
const [updateMap, setUpdateMap] = react.useState({});
|
|
910
|
+
const [expandedDescs, setExpandedDescs] = react.useState({});
|
|
911
|
+
const [installedQuery, setInstalledQuery] = react.useState("");
|
|
912
|
+
const [installedSearchOpen, setInstalledSearchOpen] = react.useState(false);
|
|
913
|
+
const [extraOnly, setExtraOnly] = react.useState(true);
|
|
914
|
+
// 点击切换时的高亮闪烁(1.5 秒后渐变回落)
|
|
915
|
+
const [extraFlash, setExtraFlash] = react.useState(false);
|
|
916
|
+
const [officialOnly, setOfficialOnly] = react.useState(false);
|
|
917
|
+
// 多源汇总搜索(GitHub + 全部自定义源并行),localStorage 持久化
|
|
918
|
+
const [multiSource, setMultiSource] = react.useState(() => {
|
|
919
|
+
try { return localStorage.getItem("pc-multi-source") === "1"; } catch { return false; }
|
|
920
|
+
});
|
|
921
|
+
const [confirmDeleteRowId, setConfirmDeleteRowId] = react.useState(null);
|
|
922
|
+
const [deleteBusy, setDeleteBusy] = react.useState(null);
|
|
923
|
+
// AI 兜底开关(默认开启,零费用保障):关掉后常规通道失败即取消,不调用模型 API
|
|
924
|
+
const [aiFallback, setAiFallback] = react.useState(() => {
|
|
925
|
+
try { return localStorage.getItem("pc-ai-fallback-v2") !== "off"; } catch { return true; }
|
|
926
|
+
});
|
|
927
|
+
const aiAutoDeclinedRef = react.useRef({});
|
|
928
|
+
// 已关闭的失败提示(用户点 × 后不再显示,服务重启后重置)
|
|
929
|
+
const [dismissedFailures, setDismissedFailures] = react.useState({});
|
|
930
|
+
// 软件源管理
|
|
931
|
+
const [sourcesOpen, setSourcesOpen] = react.useState(false);
|
|
932
|
+
const [sourcesData, setSourcesData] = react.useState(null);
|
|
933
|
+
const [sourceName, setSourceName] = react.useState("");
|
|
934
|
+
const [sourceUrl, setSourceUrl] = react.useState("");
|
|
935
|
+
const [sourcesBusy, setSourcesBusy] = react.useState(false);
|
|
936
|
+
// registry 行内编辑
|
|
937
|
+
const [editReg, setEditReg] = react.useState(null);
|
|
938
|
+
// 搜索源列表(内置 + 自定义,来自配置)
|
|
939
|
+
const [searchSourcesList, setSearchSourcesList] = react.useState(null);
|
|
940
|
+
// 自定义搜索源添加表单
|
|
941
|
+
const [searchSourceName, setSearchSourceName] = react.useState("");
|
|
942
|
+
const [searchSourceUrl, setSearchSourceUrl] = react.useState("");
|
|
943
|
+
// 自定义源请求头(每行 "名称: 值",保存时解析)
|
|
944
|
+
const [searchSourceHeaders, setSearchSourceHeaders] = react.useState("");
|
|
945
|
+
// Gitee OAuth
|
|
946
|
+
const [giteeClientId, setGiteeClientId] = react.useState("");
|
|
947
|
+
const [giteeClientSecret, setGiteeClientSecret] = react.useState("");
|
|
948
|
+
// 派生:Gitee 登录状态
|
|
949
|
+
const giteeStatus = sourcesData !== null
|
|
950
|
+
? { clientConfigured: !!sourcesData.gitee?.clientId, hasToken: !!sourcesData.gitee?.token, login: sourcesData.gitee?.login ?? "" }
|
|
951
|
+
: null;
|
|
952
|
+
// 搜索源(默认 GitHub;点击登录标切换),localStorage 持久化
|
|
953
|
+
const [searchSource, setSearchSource] = react.useState(() => {
|
|
954
|
+
try { const s = localStorage.getItem("pc-search-source"); return s === "gitee" ? s : "github"; } catch { return "github"; }
|
|
955
|
+
});
|
|
956
|
+
const [sourceMenuOpen, setSourceMenuOpen] = react.useState(false);
|
|
957
|
+
// "不再提醒":勾选后 AI 兜底自动同意,不再弹窗(可在市场底部恢复提醒)
|
|
958
|
+
const [aiRemember, setAiRemember] = react.useState(() => {
|
|
959
|
+
try { return localStorage.getItem("pc-ai-remember") === "1"; } catch { return false; }
|
|
960
|
+
});
|
|
961
|
+
const [showBackTop, setShowBackTop] = react.useState(false);
|
|
962
|
+
const [marketCollapsed, setMarketCollapsed] = react.useState(false);
|
|
963
|
+
// 下滑超过搜索框顶部时浮现"回到搜索"↑按钮
|
|
964
|
+
react.useEffect(() => {
|
|
965
|
+
const onScroll = () => {
|
|
966
|
+
const node = document.getElementById("pc-market-search");
|
|
967
|
+
setShowBackTop(node ? node.getBoundingClientRect().top < -24 : false);
|
|
968
|
+
};
|
|
969
|
+
window.addEventListener("scroll", onScroll, true);
|
|
970
|
+
window.addEventListener("resize", onScroll);
|
|
971
|
+
onScroll();
|
|
972
|
+
return () => {
|
|
973
|
+
window.removeEventListener("scroll", onScroll, true);
|
|
974
|
+
window.removeEventListener("resize", onScroll);
|
|
975
|
+
};
|
|
976
|
+
}, []);
|
|
977
|
+
// 挂载时拉取软件源配置(搜索源菜单用,含自定义源)
|
|
978
|
+
react.useEffect(() => {
|
|
979
|
+
call("/plugin-console/sources").then(
|
|
980
|
+
(data) => setSearchSourcesList(data.sources.searchSources ?? []),
|
|
981
|
+
() => {},
|
|
982
|
+
);
|
|
983
|
+
}, []);
|
|
984
|
+
// 挂载时加载静态插件索引(GitHub 源空查询时秒开浏览,零 API 调用)
|
|
985
|
+
react.useEffect(() => {
|
|
986
|
+
call("/plugin-console/market-index").then(
|
|
987
|
+
(data) => { if (data && Array.isArray(data.items)) setIndexData({ items: data.items, skills: Array.isArray(data.skills) ? data.skills : [], loaded: true }); },
|
|
988
|
+
() => {},
|
|
989
|
+
);
|
|
990
|
+
}, []);
|
|
991
|
+
// 挂载时记录原始标题(AI 授权标题闪烁后恢复用)
|
|
992
|
+
react.useEffect(() => {
|
|
993
|
+
try { originalTitleRef.current = document.title; } catch {}
|
|
994
|
+
}, []);
|
|
995
|
+
// 挂载时拉取已安装技能清单(技能条目「已装」标记用)
|
|
996
|
+
react.useEffect(() => {
|
|
997
|
+
call("/plugin-console/skills-installed").then(
|
|
998
|
+
(data) => { if (data && Array.isArray(data.skills)) setInstalledSkills(data.skills); },
|
|
999
|
+
() => {},
|
|
1000
|
+
);
|
|
1001
|
+
}, []);
|
|
1002
|
+
// 自动版本比对:市场数据中已安装的条目,后台查 registry 最新版,有新版则卡片显示「更新」。
|
|
1003
|
+
// 官方框架组件(@deepseek-ai/* 且 repository 指向 deepseek-harness)随框架版本配套发布,
|
|
1004
|
+
// 单独更新会版本混搭——排除;且必须 latest !== 已装版本才显示(避免恒显示「更新」)。
|
|
1005
|
+
react.useEffect(() => {
|
|
1006
|
+
if (market === null || market.status !== "ready" || state.status !== "ready") return;
|
|
1007
|
+
const matched = market.data
|
|
1008
|
+
.map((item) => ({
|
|
1009
|
+
item,
|
|
1010
|
+
entry: state.data.entries.find((candidate) =>
|
|
1011
|
+
(candidate.repository && candidate.repository.includes(item.fullName.toLowerCase()))
|
|
1012
|
+
|| moduleShortName(candidate.moduleName) === item.fullName.toLowerCase().split("/")[1]),
|
|
1013
|
+
}))
|
|
1014
|
+
.filter(({ entry }) => entry !== undefined && entry.moduleName !== undefined)
|
|
1015
|
+
// 排除官方框架组件:@deepseek-ai/* 且仓库指向 deepseek-harness(框架本体及其配套子包)
|
|
1016
|
+
.filter(({ entry }) => !(entry.repository && entry.repository.includes("deepseek-ai/deepseek-harness")
|
|
1017
|
+
&& String(entry.moduleName).startsWith("@deepseek-ai/")))
|
|
1018
|
+
.slice(0, 6);
|
|
1019
|
+
// 框架本体特判:deepseek-harness 仓库 → 查 @deepseek-ai/dsh(框架包)版本,
|
|
1020
|
+
// 有新版则卡片显示「框架升级 → vX」(点击走 /framework-upgrade:备份配置 + 升级指引 + 适配)
|
|
1021
|
+
const fwItem = market.data.find((item) => item.fullName === "deepseek-ai/deepseek-harness");
|
|
1022
|
+
if (fwItem) {
|
|
1023
|
+
call("/plugin-console/check-update", { packageName: "@deepseek-ai/dsh" }).then(
|
|
1024
|
+
(data) => {
|
|
1025
|
+
if (data && data.error === null) {
|
|
1026
|
+
const current = state.data.compat?.dshVersion ?? "";
|
|
1027
|
+
// 版本号比较(防 current 已是 rc.8 时误判 rc.7 为升级目标;rc.N 数字比较)
|
|
1028
|
+
const verNum = (v) => { const m = String(v).match(/0\.1\.0-rc\.(\d+)/u); return m ? parseInt(m[1], 10) : -1; };
|
|
1029
|
+
// 稳定版 latest 优先;latest 不高于当前而 next(预发布渠道)更高时,目标取 next
|
|
1030
|
+
const target = data.latest && verNum(data.latest) > verNum(current)
|
|
1031
|
+
? data.latest
|
|
1032
|
+
: (data.next && verNum(data.next) > verNum(current) ? data.next : null);
|
|
1033
|
+
if (target) setUpdateMap((prev) => ({ ...prev, [fwItem.fullName]: target }));
|
|
1034
|
+
}
|
|
1035
|
+
},
|
|
1036
|
+
() => {},
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
if (matched.length === 0) return;
|
|
1040
|
+
let done = 0;
|
|
1041
|
+
const run = () => {
|
|
1042
|
+
if (done >= matched.length) return;
|
|
1043
|
+
const { item, entry } = matched[done];
|
|
1044
|
+
done += 1;
|
|
1045
|
+
call("/plugin-console/check-update", { packageName: entry.moduleName }).then(
|
|
1046
|
+
(data) => {
|
|
1047
|
+
if (data && data.latest && data.error === null && data.latest !== entry.version) {
|
|
1048
|
+
setUpdateMap((prev) => ({ ...prev, [item.fullName]: data.latest }));
|
|
1049
|
+
}
|
|
1050
|
+
},
|
|
1051
|
+
() => {},
|
|
1052
|
+
).then(run, run);
|
|
1053
|
+
};
|
|
1054
|
+
run();
|
|
1055
|
+
}, [market, state]);
|
|
1056
|
+
// 搜索源菜单:点击外部收起
|
|
1057
|
+
react.useEffect(() => {
|
|
1058
|
+
if (!sourceMenuOpen) return;
|
|
1059
|
+
const onDown = (event) => {
|
|
1060
|
+
const area = document.getElementById("pc-ghwrap");
|
|
1061
|
+
if (area && !area.contains(event.target)) setSourceMenuOpen(false);
|
|
1062
|
+
};
|
|
1063
|
+
document.addEventListener("mousedown", onDown);
|
|
1064
|
+
return () => document.removeEventListener("mousedown", onDown);
|
|
1065
|
+
}, [sourceMenuOpen]);
|
|
1066
|
+
// 已安装搜索:点击搜索区以外且无内容时立即收起(无延迟)
|
|
1067
|
+
react.useEffect(() => {
|
|
1068
|
+
if (!installedSearchOpen) return;
|
|
1069
|
+
const onDown = (event) => {
|
|
1070
|
+
const area = document.getElementById("pc-installed-search-area");
|
|
1071
|
+
if (area && !area.contains(event.target) && installedQuery.trim() === "") {
|
|
1072
|
+
setInstalledSearchOpen(false);
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
1075
|
+
document.addEventListener("mousedown", onDown);
|
|
1076
|
+
return () => document.removeEventListener("mousedown", onDown);
|
|
1077
|
+
}, [installedSearchOpen, installedQuery]);
|
|
1078
|
+
const refresh = react.useCallback(() => {
|
|
1079
|
+
setState((prev) => ({ ...prev, status: "loading" }));
|
|
1080
|
+
call("/plugin-console/state").then(
|
|
1081
|
+
(data) => {
|
|
1082
|
+
setState({ status: "ready", data });
|
|
1083
|
+
// 恢复进行中的安装任务轮询(离开面板再回来也能看到进度)
|
|
1084
|
+
for (const job of data.installJobs ?? []) {
|
|
1085
|
+
setJobs((prev) => ({ ...prev, [job.jobId]: job }));
|
|
1086
|
+
pollJob(job.jobId);
|
|
1087
|
+
}
|
|
1088
|
+
},
|
|
1089
|
+
() => setState({ status: "error" }),
|
|
1090
|
+
);
|
|
1091
|
+
}, []);
|
|
1092
|
+
react.useEffect(() => { refresh(); }, [refresh]);
|
|
1093
|
+
const loadDetails = (entry) => {
|
|
1094
|
+
if (details !== null && details.entryId === entry.entryId) {
|
|
1095
|
+
setDetails(null);
|
|
1096
|
+
setUpdateCheck(null);
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
setDetails({ entryId: entry.entryId, status: "loading" });
|
|
1100
|
+
setUpdateCheck(null);
|
|
1101
|
+
call("/plugin-console/details", { entryId: entry.entryId }).then(
|
|
1102
|
+
(data) => setDetails({ entryId: entry.entryId, status: "ready", data }),
|
|
1103
|
+
(error) => setDetails({ entryId: entry.entryId, status: "error", error }),
|
|
1104
|
+
);
|
|
1105
|
+
};
|
|
1106
|
+
const checkUpdate = (entry) => {
|
|
1107
|
+
const pkgName = entry.moduleName;
|
|
1108
|
+
setUpdateCheck({ status: "checking" });
|
|
1109
|
+
call("/plugin-console/check-update", { packageName: pkgName }).then(
|
|
1110
|
+
(data) => setUpdateCheck({ status: "ready", latest: data.latest ?? null, source: data.source ?? "npm", error: data.error ?? null }),
|
|
1111
|
+
(error) => setUpdateCheck({ status: "ready", latest: null, source: "npm", error: friendlyGithubError(error).message }),
|
|
1112
|
+
);
|
|
1113
|
+
};
|
|
1114
|
+
const toggle = (entry, enabled) => {
|
|
1115
|
+
setBusy(entry.entryId);
|
|
1116
|
+
setMessage(null);
|
|
1117
|
+
call("/plugin-console/toggle", { entryId: entry.entryId, enabled }).then(
|
|
1118
|
+
() => {
|
|
1119
|
+
setMessage(t(enabled ? "toggledOn" : "toggledOff") + ":" + entry.entryId + "。" + t("autoReload"));
|
|
1120
|
+
// HMR 应用补丁后自动强刷页面,让客户端插件的挂载/卸载即时可见
|
|
1121
|
+
window.setTimeout(() => window.location.reload(), 1500);
|
|
1122
|
+
},
|
|
1123
|
+
(error) => setMessage(t("failed") + ":" + friendlyGithubError(error).message),
|
|
1124
|
+
).finally(() => setBusy(null));
|
|
1125
|
+
};
|
|
1126
|
+
const search = () => {
|
|
1127
|
+
setMarket({ status: "loading" });
|
|
1128
|
+
setRepoInfo(null);
|
|
1129
|
+
setMarketPage(1);
|
|
1130
|
+
// 技能模式:仅 GitHub 源;空查询 → 技能静态索引(秒开),非空 → 服务端三 topic 合并检索
|
|
1131
|
+
if (mode === "skills") {
|
|
1132
|
+
if (searchSource !== "github" || multiSource) {
|
|
1133
|
+
setMarket({ status: "error", error: new Error(t("skillsGithubOnly")) });
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
if (query.trim() === "" && indexData !== null && indexData.loaded) {
|
|
1137
|
+
setMarket({ status: "ready", data: indexData.skills.slice(0, indexShown), direct: false, source: "github", fromIndex: true });
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
call("/plugin-console/search", { q: query, page: 1, skills: true }).then(
|
|
1141
|
+
(data) => { setMarket({ status: "ready", data: data.items, direct: false, source: "github", skills: true }); },
|
|
1142
|
+
(error) => setMarket({ status: "error", error: friendlyGithubError(error) }),
|
|
1143
|
+
);
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
// 静态索引浏览:GitHub 源 + 空查询时展示 dsh-plugin topic 全量索引(秒开、零 API 调用)
|
|
1147
|
+
if (searchSource === "github" && !multiSource && query.trim() === "" && indexData !== null && indexData.loaded) {
|
|
1148
|
+
setMarket({ status: "ready", data: indexData.items.slice(0, indexShown), direct: false, source: "github", fromIndex: true });
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
if (multiSource) {
|
|
1152
|
+
// 多源汇总:GitHub + 全部自定义源并行(服务端合并,自带标记)
|
|
1153
|
+
call("/plugin-console/search", { q: query, page: 1, multi: true }).then(
|
|
1154
|
+
(data) => {
|
|
1155
|
+
setMarket({ status: "ready", data: data.items, direct: false, source: "all", multi: true });
|
|
1156
|
+
},
|
|
1157
|
+
(error) => setMarket({ status: "error", error: friendlyGithubError(error) }),
|
|
1158
|
+
);
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
if (searchSource !== "github") {
|
|
1162
|
+
// Gitee / 自定义源:走服务端平台检索(浏览器直连通道对这些平台不可靠)
|
|
1163
|
+
call("/plugin-console/search", { q: query, page: 1, source: searchSource }).then(
|
|
1164
|
+
(data) => {
|
|
1165
|
+
setMarket({ status: "ready", data: data.items, direct: false, source: searchSource });
|
|
1166
|
+
enrichOfficialBundle(data.items).then((enriched) => {
|
|
1167
|
+
setMarket((m) => (m !== null && m.status === "ready" ? { ...m, data: enriched } : m));
|
|
1168
|
+
});
|
|
1169
|
+
},
|
|
1170
|
+
(error) => setMarket({ status: "error", error: friendlyGithubError(error) }),
|
|
1171
|
+
);
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
searchFromGithub(query, 1).then(
|
|
1175
|
+
(data) => {
|
|
1176
|
+
// 秒出直连数据
|
|
1177
|
+
setMarket({ status: "ready", data, direct: true });
|
|
1178
|
+
// 并行补标记:
|
|
1179
|
+
// ① 客户端浏览器直连读根包 package.json(快,官方/聚合标记立即可筛)
|
|
1180
|
+
enrichOfficialBundle(data).then((enriched) => {
|
|
1181
|
+
setMarket((m) => (m !== null && m.status === "ready" ? { ...m, data: enriched } : m));
|
|
1182
|
+
});
|
|
1183
|
+
// ② 服务端 curl 双通道 enrich(聚合子包 dsh.bundle 检查 → aggregateInstallable),后台补更精确标记
|
|
1184
|
+
call("/plugin-console/enrich", { items: data }).then(
|
|
1185
|
+
(r) => { if (r && Array.isArray(r.items)) setMarket((m) => (m !== null && m.status === "ready" ? { ...m, data: r.items } : m)); },
|
|
1186
|
+
() => {},
|
|
1187
|
+
);
|
|
1188
|
+
},
|
|
1189
|
+
() => call("/plugin-console/search", { q: query, page: 1 }).then(
|
|
1190
|
+
(data) => {
|
|
1191
|
+
const items = data.items;
|
|
1192
|
+
setMarket({ status: "ready", data: items, direct: false });
|
|
1193
|
+
enrichOfficialBundle(items).then((enriched) => {
|
|
1194
|
+
setMarket((m) => (m !== null && m.status === "ready" ? { ...m, data: enriched } : m));
|
|
1195
|
+
});
|
|
1196
|
+
},
|
|
1197
|
+
(error) => setMarket({ status: "error", error: friendlyGithubError(error) }),
|
|
1198
|
+
),
|
|
1199
|
+
);
|
|
1200
|
+
};
|
|
1201
|
+
const loadMore = () => {
|
|
1202
|
+
if (loadingMore || market === null || market.status !== "ready") return;
|
|
1203
|
+
const next = marketPage + 1;
|
|
1204
|
+
setLoadingMore(true);
|
|
1205
|
+
setMarketPage(next);
|
|
1206
|
+
if (mode === "skills" && !market.fromIndex) {
|
|
1207
|
+
// 技能搜索分页:服务端三 topic 合并(skills: true)
|
|
1208
|
+
call("/plugin-console/search", { q: query, page: next, skills: true }).then(
|
|
1209
|
+
(data) => {
|
|
1210
|
+
const merged = [...market.data, ...data.items];
|
|
1211
|
+
const seen = new Set();
|
|
1212
|
+
const deduped = [];
|
|
1213
|
+
for (const item of merged) {
|
|
1214
|
+
if (seen.has(item.fullName)) continue;
|
|
1215
|
+
seen.add(item.fullName);
|
|
1216
|
+
deduped.push(item);
|
|
1217
|
+
}
|
|
1218
|
+
setMarket({ status: "ready", data: deduped, direct: false, source: "github", skills: true });
|
|
1219
|
+
setLoadingMore(false);
|
|
1220
|
+
},
|
|
1221
|
+
(error) => {
|
|
1222
|
+
setLoadingMore(false);
|
|
1223
|
+
setMessage(t("failed") + ":" + friendlyGithubError(error).message);
|
|
1224
|
+
},
|
|
1225
|
+
);
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
if (market.fromIndex === true && indexData !== null) {
|
|
1229
|
+
// 索引浏览:本地继续展示更多(技能模式浏览技能索引段)
|
|
1230
|
+
const sourceData = mode === "skills" ? indexData.skills : indexData.items;
|
|
1231
|
+
const more = sourceData.slice(0, indexShown + next * 50);
|
|
1232
|
+
setMarket({ status: "ready", data: more, direct: false, source: "github", fromIndex: true });
|
|
1233
|
+
setIndexShown(indexShown + next * 50);
|
|
1234
|
+
setLoadingMore(false);
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
if (multiSource) {
|
|
1238
|
+
call("/plugin-console/search", { q: query, page: next, multi: true }).then(
|
|
1239
|
+
(data) => {
|
|
1240
|
+
setMarket({ status: "ready", data: [...market.data, ...data.items], direct: false, source: "all", multi: true });
|
|
1241
|
+
setLoadingMore(false);
|
|
1242
|
+
},
|
|
1243
|
+
(error) => {
|
|
1244
|
+
setLoadingMore(false);
|
|
1245
|
+
setMessage(t("failed") + ":" + friendlyGithubError(error).message);
|
|
1246
|
+
},
|
|
1247
|
+
);
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
if (searchSource !== "github") {
|
|
1251
|
+
call("/plugin-console/search", { q: query, page: next, source: searchSource }).then(
|
|
1252
|
+
(data) => {
|
|
1253
|
+
setMarket({ status: "ready", data: [...market.data, ...data.items], direct: false, source: searchSource });
|
|
1254
|
+
setLoadingMore(false);
|
|
1255
|
+
enrichOfficialBundle(data.items).then((enriched) => {
|
|
1256
|
+
setMarket((m) => (m !== null && m.status === "ready"
|
|
1257
|
+
? { ...m, data: [...m.data.slice(0, m.data.length - enriched.length), ...enriched] }
|
|
1258
|
+
: m));
|
|
1259
|
+
});
|
|
1260
|
+
},
|
|
1261
|
+
(error) => {
|
|
1262
|
+
setLoadingMore(false);
|
|
1263
|
+
setMessage(t("failed") + ":" + friendlyGithubError(error).message);
|
|
1264
|
+
},
|
|
1265
|
+
);
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
searchFromGithub(query, next).then(
|
|
1269
|
+
(data) => {
|
|
1270
|
+
setMarket({ status: "ready", data: [...market.data, ...data], direct: true });
|
|
1271
|
+
setLoadingMore(false);
|
|
1272
|
+
// 并行补标记:客户端快速(根包)+ 服务端完整(子包检查)
|
|
1273
|
+
enrichOfficialBundle(data).then((enriched) => {
|
|
1274
|
+
setMarket((m) => (m !== null && m.status === "ready"
|
|
1275
|
+
? { ...m, data: [...m.data.slice(0, m.data.length - enriched.length), ...enriched] }
|
|
1276
|
+
: m));
|
|
1277
|
+
});
|
|
1278
|
+
call("/plugin-console/enrich", { items: data }).then(
|
|
1279
|
+
(r) => {
|
|
1280
|
+
if (r && Array.isArray(r.items)) {
|
|
1281
|
+
setMarket((m) => (m !== null && m.status === "ready"
|
|
1282
|
+
? { ...m, data: [...m.data.slice(0, m.data.length - r.items.length), ...r.items] }
|
|
1283
|
+
: m));
|
|
1284
|
+
}
|
|
1285
|
+
},
|
|
1286
|
+
() => {},
|
|
1287
|
+
);
|
|
1288
|
+
},
|
|
1289
|
+
() => call("/plugin-console/search", { q: query, page: next }).then(
|
|
1290
|
+
(data) => {
|
|
1291
|
+
setMarket({ status: "ready", data: [...market.data, ...data.items], direct: false });
|
|
1292
|
+
setLoadingMore(false);
|
|
1293
|
+
enrichOfficialBundle(data.items).then((enriched) => {
|
|
1294
|
+
setMarket((m) => (m !== null && m.status === "ready"
|
|
1295
|
+
? { ...m, data: [...m.data.slice(0, m.data.length - enriched.length), ...enriched] }
|
|
1296
|
+
: m));
|
|
1297
|
+
});
|
|
1298
|
+
},
|
|
1299
|
+
(error) => {
|
|
1300
|
+
setLoadingMore(false);
|
|
1301
|
+
setMessage(t("failed") + ":" + friendlyGithubError(error).message);
|
|
1302
|
+
},
|
|
1303
|
+
),
|
|
1304
|
+
);
|
|
1305
|
+
};
|
|
1306
|
+
const loadSubpackages = (repo, branch) => {
|
|
1307
|
+
setSubpackages({ status: "loading" });
|
|
1308
|
+
fetchSubpackages(repo, branch).then(
|
|
1309
|
+
(list) => setSubpackages({ status: "ready", list }),
|
|
1310
|
+
() => call("/plugin-console/subpackages", { repo, branch }).then(
|
|
1311
|
+
(data) => setSubpackages({ status: "ready", list: data.subpackages ?? [] }),
|
|
1312
|
+
() => setSubpackages({ status: "error" }),
|
|
1313
|
+
),
|
|
1314
|
+
);
|
|
1315
|
+
};
|
|
1316
|
+
const inspect = (item) => {
|
|
1317
|
+
const repo = item.fullName;
|
|
1318
|
+
setRepoInfo({ status: "loading", repo });
|
|
1319
|
+
setSubpackages(null);
|
|
1320
|
+
if (item.source !== undefined && item.source !== "github") {
|
|
1321
|
+
// 非 GitHub 平台:用平台 raw 直接读详情(无 trees/子包能力)
|
|
1322
|
+
repoInfoFromPlatform(item).then(
|
|
1323
|
+
(data) => setRepoInfo({ status: "ready", repo, data, direct: true, source: item.source }),
|
|
1324
|
+
() => setRepoInfo({ status: "error", repo, error: new Error(t("repoError")) }),
|
|
1325
|
+
);
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
repoInfoFromGithub(repo, item).then(
|
|
1329
|
+
(data) => {
|
|
1330
|
+
if (item.hasSkill === true) data.hasSkill = true;
|
|
1331
|
+
if (Array.isArray(item.skillTopics)) data.skillTopics = item.skillTopics;
|
|
1332
|
+
if (item.hasSuite === true) {
|
|
1333
|
+
data.hasSuite = true;
|
|
1334
|
+
// 立即本地拼官方安装命令(不等服务端后台补拉),手动安装引导即时可见
|
|
1335
|
+
if (!data.installCommand) {
|
|
1336
|
+
const short = repo.split("/")[1] ?? repo;
|
|
1337
|
+
data.installCommand = "git -c http.sslVerify=false clone --recurse-submodules https://github.com/" + repo + ".git\ncd " + short + "\npowershell -ExecutionPolicy Bypass -File install.ps1";
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
setRepoInfo({ status: "ready", repo, data, direct: true });
|
|
1341
|
+
if (data.privateRoot || !data.hasPackageJson) loadSubpackages(repo, data.defaultBranch);
|
|
1342
|
+
// 浏览器直连只读 package.json/README/SKILL.md:后台补拉服务端增强字段
|
|
1343
|
+
// (hasSuite / installCommand 官方安装方式),不阻塞详情显示
|
|
1344
|
+
call("/plugin-console/repo", { repo }).then(
|
|
1345
|
+
(d) => {
|
|
1346
|
+
if (d && d.ok === true) {
|
|
1347
|
+
setRepoInfo((prev) => (prev !== null && prev.repo === repo && prev.status === "ready"
|
|
1348
|
+
? { ...prev, data: {
|
|
1349
|
+
...prev.data,
|
|
1350
|
+
hasSuite: d.hasSuite === true,
|
|
1351
|
+
installCommand: d.installCommand ?? null,
|
|
1352
|
+
} }
|
|
1353
|
+
: prev));
|
|
1354
|
+
}
|
|
1355
|
+
},
|
|
1356
|
+
() => {},
|
|
1357
|
+
);
|
|
1358
|
+
},
|
|
1359
|
+
() => call("/plugin-console/repo", { repo }).then(
|
|
1360
|
+
(data) => {
|
|
1361
|
+
if (item.hasSkill === true) data.hasSkill = true;
|
|
1362
|
+
if (Array.isArray(item.skillTopics)) data.skillTopics = item.skillTopics;
|
|
1363
|
+
if (item.hasSuite === true) data.hasSuite = true;
|
|
1364
|
+
setRepoInfo({ status: "ready", repo, data, direct: false });
|
|
1365
|
+
if (data.privateRoot || !data.hasPackageJson) loadSubpackages(repo, data.defaultBranch);
|
|
1366
|
+
},
|
|
1367
|
+
(error) => setRepoInfo({ status: "error", repo, error: friendlyGithubError(error) }),
|
|
1368
|
+
),
|
|
1369
|
+
);
|
|
1370
|
+
};
|
|
1371
|
+
/** 后台安装任务:/install 立即返回 jobId,轮询 /install-status 更新进度。 */
|
|
1372
|
+
const jobTimersRef = react.useRef({});
|
|
1373
|
+
const stopJobPolling = (jobId) => {
|
|
1374
|
+
if (jobTimersRef.current[jobId]) {
|
|
1375
|
+
window.clearInterval(jobTimersRef.current[jobId]);
|
|
1376
|
+
delete jobTimersRef.current[jobId];
|
|
1377
|
+
}
|
|
1378
|
+
};
|
|
1379
|
+
const pollJob = (jobId) => {
|
|
1380
|
+
if (jobTimersRef.current[jobId]) return;
|
|
1381
|
+
jobTimersRef.current[jobId] = window.setInterval(() => {
|
|
1382
|
+
call("/plugin-console/install-status", { jobId }).then(
|
|
1383
|
+
(data) => {
|
|
1384
|
+
setJobs((prev) => ({ ...prev, [jobId]: data }));
|
|
1385
|
+
// 需要用户授权时(面板页后台/失焦也能看到):系统通知 + 标题闪烁提示
|
|
1386
|
+
if (data.status === "installing" && data.stage === "ai-consent" && aiFallback && !aiRemember && !aiAutoDeclinedRef.current[jobId]) {
|
|
1387
|
+
aiAutoDeclinedRef.current[jobId] = true;
|
|
1388
|
+
const who = data.packageName ?? data.repo ?? "";
|
|
1389
|
+
try {
|
|
1390
|
+
const title = t("aiConsentNotifyTitle");
|
|
1391
|
+
if ("Notification" in window && Notification.permission === "granted") {
|
|
1392
|
+
new Notification(title, { body: who + ":" + t("aiConsentText").slice(0, 80) + "…", tag: "dsh-ai-consent" });
|
|
1393
|
+
} else if ("Notification" in window && Notification.permission === "default") {
|
|
1394
|
+
Notification.requestPermission().then((p) => {
|
|
1395
|
+
if (p === "granted") new Notification(title, { body: who + ":" + t("aiConsentText").slice(0, 80) + "…", tag: "dsh-ai-consent" });
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
} catch {}
|
|
1399
|
+
try {
|
|
1400
|
+
document.title = "⚠ " + t("aiConsentNotifyTitle") + " - " + who;
|
|
1401
|
+
} catch {}
|
|
1402
|
+
}
|
|
1403
|
+
// AI 兜底已关闭:遇到授权等待态自动取消(零费用,不弹窗)
|
|
1404
|
+
if (data.status === "installing" && data.stage === "ai-consent" && !aiFallback && !aiAutoDeclinedRef.current[jobId]) {
|
|
1405
|
+
aiAutoDeclinedRef.current[jobId] = true;
|
|
1406
|
+
aiConsent(jobId, false);
|
|
1407
|
+
}
|
|
1408
|
+
// 用户勾选"不再提醒":自动同意 AI 兜底(已预先授权,可能产生费用)
|
|
1409
|
+
if (data.status === "installing" && data.stage === "ai-consent" && aiFallback && aiRemember && !aiAutoDeclinedRef.current[jobId]) {
|
|
1410
|
+
aiAutoDeclinedRef.current[jobId] = true;
|
|
1411
|
+
aiConsent(jobId, true);
|
|
1412
|
+
}
|
|
1413
|
+
if (data.status !== "installing") {
|
|
1414
|
+
stopJobPolling(jobId);
|
|
1415
|
+
if (data.status === "done") {
|
|
1416
|
+
if (data.kind === "skill") {
|
|
1417
|
+
// 技能安装:不写补丁、无需重启/刷新页面;提示 + 刷新已装技能清单
|
|
1418
|
+
setMessage(t("skillInstalledMsg") + ":" + (data.skillName ?? "") + "。" + (data.skillNote ?? t("skillInstallNote")));
|
|
1419
|
+
call("/plugin-console/skills-installed").then(
|
|
1420
|
+
(d) => { if (d && Array.isArray(d.skills)) setInstalledSkills(d.skills); },
|
|
1421
|
+
() => {},
|
|
1422
|
+
);
|
|
1423
|
+
} else if (data.kind === "suite") {
|
|
1424
|
+
// 套装安装:保存组件报告;有 bundle 组件需重启,否则刷新页面
|
|
1425
|
+
if (Array.isArray(data.suiteReport)) setSuiteReports((prev) => ({ ...prev, [jobId]: data.suiteReport }));
|
|
1426
|
+
setMessage(t("suiteInstalledMsg") + ":" + (data.suiteNote ?? ""));
|
|
1427
|
+
if ((data.suiteReport ?? []).some((r) => r.type === "bundle" && r.ok)) {
|
|
1428
|
+
setRestartHint(true);
|
|
1429
|
+
} else {
|
|
1430
|
+
setReloadHint(true);
|
|
1431
|
+
window.setTimeout(() => window.location.reload(), 2500);
|
|
1432
|
+
}
|
|
1433
|
+
} else if (data.ai === true) {
|
|
1434
|
+
setMessage(t("installed") + ":" + data.packageName + "。" + (data.aiNote ?? t("aiRepaired")));
|
|
1435
|
+
} else {
|
|
1436
|
+
setMessage(t("installed") + ":" + data.packageName + "(" + (data.entryId ?? "") + ")。" + (data.curlNote ?? (data.bundle ? t("bundleNote") : t("installNote"))) + (data.bundleNote ? "。" + data.bundleNote : ""));
|
|
1437
|
+
}
|
|
1438
|
+
if (data.kind === "skill") {
|
|
1439
|
+
// 技能无需页面刷新
|
|
1440
|
+
setInstalling(null);
|
|
1441
|
+
setTimeout(refresh, 500);
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
if (data.bundle) {
|
|
1445
|
+
setRestartHint(true);
|
|
1446
|
+
} else {
|
|
1447
|
+
// 安装成功:自动刷新页面,让插件立即出现在列表里(可见的成功反馈)
|
|
1448
|
+
setReloadHint(true);
|
|
1449
|
+
window.setTimeout(() => window.location.reload(), 2500);
|
|
1450
|
+
}
|
|
1451
|
+
} else {
|
|
1452
|
+
setMessage(t("failed") + ":" + (data.error ?? "未知错误"));
|
|
1453
|
+
}
|
|
1454
|
+
setInstalling(null);
|
|
1455
|
+
setTimeout(refresh, 1500);
|
|
1456
|
+
}
|
|
1457
|
+
},
|
|
1458
|
+
() => {},
|
|
1459
|
+
);
|
|
1460
|
+
}, 2000);
|
|
1461
|
+
};
|
|
1462
|
+
/** 本地 AI 兜底授权:调用模型 API 产生费用,必须用户明确同意。 */
|
|
1463
|
+
const aiConsent = (jobId, approved) => {
|
|
1464
|
+
call("/plugin-console/ai-consent", { jobId, approved }).then(
|
|
1465
|
+
() => { if (!approved) setMessage(t("failed") + ":已取消本地 AI 兜底(不会调用模型 API)"); },
|
|
1466
|
+
(error) => setMessage(t("failed") + ":" + friendlyGithubError(error).message),
|
|
1467
|
+
);
|
|
1468
|
+
};
|
|
1469
|
+
/** 软件源管理:打开时拉取当前配置。 */
|
|
1470
|
+
const openSources = () => {
|
|
1471
|
+
setSourcesOpen(true);
|
|
1472
|
+
call("/plugin-console/sources").then(
|
|
1473
|
+
(data) => {
|
|
1474
|
+
setSourcesData(data.sources);
|
|
1475
|
+
setSearchSourcesList(data.sources.searchSources ?? []);
|
|
1476
|
+
setGiteeClientId(data.sources.gitee?.clientId ?? "");
|
|
1477
|
+
setGiteeClientSecret(data.sources.gitee?.clientSecret ?? "");
|
|
1478
|
+
},
|
|
1479
|
+
(error) => { setSourcesOpen(false); setMessage(t("failed") + ":" + friendlyGithubError(error).message); },
|
|
1480
|
+
);
|
|
1481
|
+
};
|
|
1482
|
+
const saveGiteeSetup = () => {
|
|
1483
|
+
if (!giteeClientId.trim() || !giteeClientSecret.trim()) {
|
|
1484
|
+
setMessage(t("failed") + ":client_id / client_secret 不能为空");
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
sourcesAction({ action: "gitee-setup", clientId: giteeClientId.trim(), clientSecret: giteeClientSecret.trim() });
|
|
1488
|
+
};
|
|
1489
|
+
const startGiteeOauth = () => {
|
|
1490
|
+
call("/plugin-console/gitee-oauth-url").then(
|
|
1491
|
+
(data) => { if (data && data.url) window.open(data.url, "_blank", "noopener"); },
|
|
1492
|
+
(error) => setMessage(t("failed") + ":" + friendlyGithubError(error).message),
|
|
1493
|
+
);
|
|
1494
|
+
};
|
|
1495
|
+
const sourcesAction = (payload, done) => {
|
|
1496
|
+
setSourcesBusy(true);
|
|
1497
|
+
call("/plugin-console/sources", payload).then(
|
|
1498
|
+
(data) => {
|
|
1499
|
+
setSourcesData(data.sources);
|
|
1500
|
+
setSearchSourcesList(data.sources.searchSources ?? []);
|
|
1501
|
+
setMessage(t("sourcesUpdated"));
|
|
1502
|
+
if (done) done();
|
|
1503
|
+
},
|
|
1504
|
+
(error) => setMessage(t("failed") + ":" + friendlyGithubError(error).message),
|
|
1505
|
+
).finally(() => setSourcesBusy(false));
|
|
1506
|
+
};
|
|
1507
|
+
const addSource = () => {
|
|
1508
|
+
if (!isAllowedSourceUrl(sourceUrl.trim())) {
|
|
1509
|
+
setMessage(t("failed") + ":" + t("invalidSourceUrl"));
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
sourcesAction({ action: "add", name: sourceName.trim(), url: sourceUrl.trim() }, () => { setSourceName(""); setSourceUrl(""); });
|
|
1513
|
+
};
|
|
1514
|
+
const saveEditReg = () => {
|
|
1515
|
+
if (editReg === null) return;
|
|
1516
|
+
sourcesAction({ action: "edit", id: editReg.id, name: editReg.name, url: editReg.url }, () => setEditReg(null));
|
|
1517
|
+
};
|
|
1518
|
+
const addSearchSource = () => {
|
|
1519
|
+
if (!isAllowedSourceUrl(searchSourceUrl.trim()) || !searchSourceUrl.includes("{q}")) {
|
|
1520
|
+
setMessage(t("failed") + ":" + t("invalidSearchUrl"));
|
|
1521
|
+
return;
|
|
1522
|
+
}
|
|
1523
|
+
// 解析请求头:每行 "名称: 值"
|
|
1524
|
+
const headers = searchSourceHeaders.split("\n")
|
|
1525
|
+
.map((line) => line.trim())
|
|
1526
|
+
.filter((line) => line !== "")
|
|
1527
|
+
.map((line) => {
|
|
1528
|
+
const idx = line.indexOf(":");
|
|
1529
|
+
if (idx <= 0) return null;
|
|
1530
|
+
return { name: line.slice(0, idx).trim(), value: line.slice(idx + 1).trim() };
|
|
1531
|
+
})
|
|
1532
|
+
.filter((h) => h !== null);
|
|
1533
|
+
sourcesAction(
|
|
1534
|
+
{ action: "add-search", name: searchSourceName.trim(), url: searchSourceUrl.trim(), headers },
|
|
1535
|
+
() => { setSearchSourceName(""); setSearchSourceUrl(""); setSearchSourceHeaders(""); },
|
|
1536
|
+
);
|
|
1537
|
+
};
|
|
1538
|
+
const startJob = (repo, packageName, source = "github", kind = "plugin") => {
|
|
1539
|
+
// 防并发(与 addLocal 一致):已有安装/更新进行中时忽略新点击(更新按钮此前可绕过,
|
|
1540
|
+
// 反复点击会产生几十个并发任务装同一插件)
|
|
1541
|
+
if (installing !== null) {
|
|
1542
|
+
setMessage(t("installBusy"));
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
setInstalling(repo || packageName);
|
|
1546
|
+
setReloadHint(false);
|
|
1547
|
+
setRestartHint(false);
|
|
1548
|
+
setMessage(null);
|
|
1549
|
+
call("/plugin-console/install", { repo: repo ?? "", packageName, source, kind }).then(
|
|
1550
|
+
(data) => {
|
|
1551
|
+
if (data && data.jobId) {
|
|
1552
|
+
setJobs((prev) => ({
|
|
1553
|
+
...prev,
|
|
1554
|
+
[data.jobId]: { jobId: data.jobId, repo, source, packageName, kind, status: "installing", stage: "preparing", startedAt: Date.now() },
|
|
1555
|
+
}));
|
|
1556
|
+
pollJob(data.jobId);
|
|
1557
|
+
}
|
|
1558
|
+
},
|
|
1559
|
+
(error) => {
|
|
1560
|
+
setMessage(t("failed") + ":" + friendlyGithubError(error).message);
|
|
1561
|
+
setInstalling(null);
|
|
1562
|
+
},
|
|
1563
|
+
);
|
|
1564
|
+
};
|
|
1565
|
+
/** 技能安装:git clone 仓库 → 复制 SKILL.md 到 ~/.dsh/skills/<name>/(不写 cordis 补丁、无需重启)。 */
|
|
1566
|
+
const startSkillJob = (item) => {
|
|
1567
|
+
if (installing !== null) return;
|
|
1568
|
+
startJob(item.fullName, null, item.source ?? "github", "skill");
|
|
1569
|
+
};
|
|
1570
|
+
/** 套装安装:submodule 聚合仓库(照 install.ps1 语义装配子模块组件)。 */
|
|
1571
|
+
const startSuiteJob = (item) => {
|
|
1572
|
+
if (installing !== null) return;
|
|
1573
|
+
startJob(item.fullName, null, item.source ?? "github", "suite");
|
|
1574
|
+
};
|
|
1575
|
+
// 卸载时清理轮询定时器;页面回到前台时恢复标题(AI 授权提示后)
|
|
1576
|
+
react.useEffect(() => {
|
|
1577
|
+
const onVisible = () => {
|
|
1578
|
+
if (document.visibilityState === "visible") {
|
|
1579
|
+
try { document.title = originalTitleRef.current; } catch {}
|
|
1580
|
+
}
|
|
1581
|
+
};
|
|
1582
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
1583
|
+
return () => {
|
|
1584
|
+
document.removeEventListener("visibilitychange", onVisible);
|
|
1585
|
+
for (const timer of Object.values(jobTimersRef.current)) window.clearInterval(timer);
|
|
1586
|
+
};
|
|
1587
|
+
}, []);
|
|
1588
|
+
/** 搜索结果卡片上的"添加到本地":直接启动安装任务(服务端解析包名与类型),
|
|
1589
|
+
* 点击立即出现进度卡片——不再先浏览器取包名(黑洞期直连失败会拖到 40s 无反馈)。
|
|
1590
|
+
* 技能条目走技能通道,套装条目走套装通道。 */
|
|
1591
|
+
const addLocal = (item) => {
|
|
1592
|
+
if (installing !== null) return;
|
|
1593
|
+
setInstalling(item.fullName);
|
|
1594
|
+
setMessage(null);
|
|
1595
|
+
if (item.fullName === "deepseek-ai/deepseek-harness") {
|
|
1596
|
+
// 框架本体:不提供安装(服务端也会拦截);升级请用卡片「框架升级」流程
|
|
1597
|
+
setMessage(t("frameworkUseUpgrade"));
|
|
1598
|
+
setInstalling(null);
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
if (item.hasSkill === true || item.skillTopics !== undefined) {
|
|
1602
|
+
// 没有 npm 包但有 SKILL.md:按技能安装(clone → ~/.dsh/skills)
|
|
1603
|
+
startSkillJob(item);
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
if (item.hasSuite === true) {
|
|
1607
|
+
// submodule 聚合套装:走套装安装通道(clone → 子模块按类型装配)
|
|
1608
|
+
startSuiteJob(item);
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
startJob(item.fullName, null, item.source ?? "github");
|
|
1612
|
+
};
|
|
1613
|
+
/** 停用/启用已安装技能(写入官方调用策略 frontmatter,可逆)。 */
|
|
1614
|
+
const doToggleSkill = (name, enabled) => {
|
|
1615
|
+
setTogglingSkill(name);
|
|
1616
|
+
call("/plugin-console/skill-toggle", { name, enabled }).then(
|
|
1617
|
+
() => {
|
|
1618
|
+
setMessage((enabled ? t("skillEnabledMsg") : t("skillToggledMsg")) + ":" + name);
|
|
1619
|
+
setConfirmSkillDelete(null);
|
|
1620
|
+
setTogglingSkill(null);
|
|
1621
|
+
setInstalledSkills((prev) => prev.map((s) => (s.name === name ? { ...s, disabled: !enabled } : s)));
|
|
1622
|
+
},
|
|
1623
|
+
(error) => {
|
|
1624
|
+
setTogglingSkill(null);
|
|
1625
|
+
setMessage(t("failed") + ":" + friendlyGithubError(error).message);
|
|
1626
|
+
},
|
|
1627
|
+
);
|
|
1628
|
+
};
|
|
1629
|
+
/** 复制安装命令到剪贴板(navigator.clipboard + 兼容兜底)。 */
|
|
1630
|
+
const copyInstallCommand = (cmd) => {
|
|
1631
|
+
const done = () => setMessage(t("copiedInstallCmd"));
|
|
1632
|
+
const fallback = () => {
|
|
1633
|
+
try {
|
|
1634
|
+
const ta = document.createElement("textarea");
|
|
1635
|
+
ta.value = cmd;
|
|
1636
|
+
ta.style.position = "fixed";
|
|
1637
|
+
ta.style.opacity = "0";
|
|
1638
|
+
document.body.appendChild(ta);
|
|
1639
|
+
ta.select();
|
|
1640
|
+
const ok = document.execCommand("copy");
|
|
1641
|
+
document.body.removeChild(ta);
|
|
1642
|
+
if (ok) done();
|
|
1643
|
+
} catch {}
|
|
1644
|
+
};
|
|
1645
|
+
try {
|
|
1646
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
1647
|
+
navigator.clipboard.writeText(cmd).then(done, fallback);
|
|
1648
|
+
} else fallback();
|
|
1649
|
+
} catch { fallback(); }
|
|
1650
|
+
};
|
|
1651
|
+
/** 框架升级:Hub 打包备份现有配置 + 版本检测 + 升级脚本(脚本会自行停止/拉起服务,无需手动重启)。 */
|
|
1652
|
+
const doFrameworkUpgrade = () => {
|
|
1653
|
+
setFrameworkUpgrading(true);
|
|
1654
|
+
call("/plugin-console/framework-upgrade", {}).then(
|
|
1655
|
+
(data) => {
|
|
1656
|
+
setFrameworkUpgrading(false);
|
|
1657
|
+
setConfirmFrameworkUpgrade(false);
|
|
1658
|
+
if (data && data.ok === true) {
|
|
1659
|
+
// 新一轮升级开始:清除上次终态关闭标记(卡片恢复显示)
|
|
1660
|
+
try { localStorage.removeItem("pc-fw-dismiss-terminal"); } catch {}
|
|
1661
|
+
setMessage(t("frameworkUpgradeDone")
|
|
1662
|
+
+ (data.hasUpdate === true ? ":" + (data.current ?? "?") + " → " + (data.target ?? data.latest ?? "?") : (data.registryError ? t("frameworkUpgradeCheckFailed") : t("frameworkUpgradeUpToDate")))
|
|
1663
|
+
+ "。备份:" + (data.backupDir ?? "") + "。" + (Array.isArray(data.hints) ? data.hints.join(" ") : ""));
|
|
1664
|
+
// 升级脚本会自行停止→升级→拉起服务:不显示手动重启按钮(避免升级中途被干扰)
|
|
1665
|
+
setRestartHint(false);
|
|
1666
|
+
setReloadHint(false);
|
|
1667
|
+
// 启动进度条轮询(服务断开前能看到前几步;重连后自动恢复)
|
|
1668
|
+
setFrameworkStatus({ status: "starting", message: t("fwStepBackup") });
|
|
1669
|
+
pollFrameworkStatus();
|
|
1670
|
+
}
|
|
1671
|
+
},
|
|
1672
|
+
(error) => {
|
|
1673
|
+
setFrameworkUpgrading(false);
|
|
1674
|
+
setConfirmFrameworkUpgrade(false);
|
|
1675
|
+
setMessage(t("failed") + ":" + friendlyGithubError(error).message);
|
|
1676
|
+
},
|
|
1677
|
+
);
|
|
1678
|
+
};
|
|
1679
|
+
/** 删除已安装技能(~/.dsh/skills/<name> 目录),两步确认防误删。 */
|
|
1680
|
+
const doRemoveSkill = (name) => {
|
|
1681
|
+
call("/plugin-console/skill-remove", { name }).then(
|
|
1682
|
+
() => {
|
|
1683
|
+
setMessage(t("skillDeletedMsg") + ":" + name);
|
|
1684
|
+
setConfirmSkillDelete(null);
|
|
1685
|
+
setInstalledSkills((prev) => prev.filter((s) => s.name !== name));
|
|
1686
|
+
},
|
|
1687
|
+
(error) => setMessage(t("failed") + ":" + friendlyGithubError(error).message),
|
|
1688
|
+
);
|
|
1689
|
+
};
|
|
1690
|
+
const installedQueryNorm = installedQuery.trim().toLowerCase();
|
|
1691
|
+
// 当前搜索源的展示名(内置 + 自定义)
|
|
1692
|
+
const sourceDisplayName = (source) => source === "github"
|
|
1693
|
+
? "GitHub"
|
|
1694
|
+
: source === "gitee"
|
|
1695
|
+
? "Gitee"
|
|
1696
|
+
: ((searchSourcesList !== null ? searchSourcesList.find((s) => s.id === source)?.name : undefined) ?? source);
|
|
1697
|
+
const extraIds = new Set((state.status === "ready" ? state.data.patch?.inserts ?? [] : []));
|
|
1698
|
+
const extraCount = state.status === "ready" ? state.data.entries.filter((entry) => entry.extra === true || extraIds.has(entry.rowId)).length : 0;
|
|
1699
|
+
const rows = state.status === "ready"
|
|
1700
|
+
? state.data.entries.filter((entry) => {
|
|
1701
|
+
if (extraOnly && !(entry.extra === true || extraIds.has(entry.rowId))) return false;
|
|
1702
|
+
if (installedQueryNorm === "") return true;
|
|
1703
|
+
return [entry.moduleName, entry.entryId, entry.rowId]
|
|
1704
|
+
.some((value) => String(value ?? "").toLowerCase().includes(installedQueryNorm));
|
|
1705
|
+
}).map((entry) => {
|
|
1706
|
+
const open = details !== null && details.entryId === entry.entryId;
|
|
1707
|
+
let detailPanel = null;
|
|
1708
|
+
if (open) {
|
|
1709
|
+
if (details.status === "loading") {
|
|
1710
|
+
detailPanel = el("div", { className: styles.detail },
|
|
1711
|
+
el("p", { className: styles.status }, t("loadingDetails")));
|
|
1712
|
+
} else if (details.status === "error") {
|
|
1713
|
+
detailPanel = el("div", { className: styles.detail },
|
|
1714
|
+
el("p", { className: styles.status, "data-error": "true" }, t("failed") + ":" + details.error.message));
|
|
1715
|
+
} else {
|
|
1716
|
+
const meta = details.data.meta;
|
|
1717
|
+
const readme = details.data.readme;
|
|
1718
|
+
detailPanel = el("div", { className: styles.detail },
|
|
1719
|
+
meta !== null ? el("p", { className: styles.desc }, meta.description ?? t("noDetail")) : null,
|
|
1720
|
+
meta !== null && meta.version ? el("div", { className: styles.rowTop },
|
|
1721
|
+
el("p", { className: styles.status }, t("versionLabel") + ":" + meta.version),
|
|
1722
|
+
el("button", {
|
|
1723
|
+
type: "button",
|
|
1724
|
+
className: styles.toggle,
|
|
1725
|
+
disabled: (updateCheck !== null && updateCheck.status === "checking") || installing === entry.moduleName,
|
|
1726
|
+
onClick: () => checkUpdate(entry),
|
|
1727
|
+
}, updateCheck !== null && updateCheck.status === "checking" ? t("checkingUpdate") : t("checkUpdate"))) : null,
|
|
1728
|
+
updateCheck !== null && updateCheck.status === "ready"
|
|
1729
|
+
? (updateCheck.error
|
|
1730
|
+
? el("p", { className: styles.status, "data-error": "true" }, t("updateCheckFailed") + ":" + updateCheck.error)
|
|
1731
|
+
: (updateCheck.latest && meta !== null && meta.version && updateCheck.latest !== meta.version
|
|
1732
|
+
? el("div", null,
|
|
1733
|
+
el("div", { className: styles.rowTop },
|
|
1734
|
+
el("p", { className: styles.status }, t("updateAvailable") + ":" + meta.version + " → " + updateCheck.latest + (updateCheck.source === "github" ? "(GitHub release)" : "")),
|
|
1735
|
+
el("button", {
|
|
1736
|
+
type: "button",
|
|
1737
|
+
className: styles.toggle,
|
|
1738
|
+
disabled: installing === entry.moduleName,
|
|
1739
|
+
onClick: () => startJob("", entry.moduleName),
|
|
1740
|
+
}, installing === entry.moduleName ? t("installing") : t("updateNow"))),
|
|
1741
|
+
Array.isArray(updateCheck.depsOutdated) && updateCheck.depsOutdated.length > 0
|
|
1742
|
+
? el("p", { className: styles.status, "data-error": "true" },
|
|
1743
|
+
t("depsOutdatedHint") + ":" + updateCheck.depsOutdated.map((d) => `${d.name} ${d.current}→${d.required}`).join("、"))
|
|
1744
|
+
: null)
|
|
1745
|
+
: el("p", { className: styles.status }, t("upToDate") + (updateCheck.source === "github" ? "(GitHub release 检测)" : ""))))
|
|
1746
|
+
: null,
|
|
1747
|
+
meta !== null && meta.repository
|
|
1748
|
+
? el("p", { className: styles.status }, t("repoLabel") + ":" + String(meta.repository).replace(/^git\+/u, ""))
|
|
1749
|
+
: null,
|
|
1750
|
+
meta !== null && meta.homepage
|
|
1751
|
+
? el("p", { className: styles.status }, t("homepageLabel") + ":" + meta.homepage)
|
|
1752
|
+
: null,
|
|
1753
|
+
readme !== null && (readme.title || readme.summary)
|
|
1754
|
+
? el("div", null,
|
|
1755
|
+
el("p", { className: styles.status }, t("readmeLabel") + ":"),
|
|
1756
|
+
readme.title ? el("strong", null, readme.title + "。") : null,
|
|
1757
|
+
readme.summary ? el("p", { className: styles.desc }, readme.summary) : null,
|
|
1758
|
+
)
|
|
1759
|
+
: meta === null ? el("p", { className: styles.status }, t("noDetail")) : null,
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
return el("li", { key: entry.entryId, className: styles.row },
|
|
1764
|
+
el("div", { className: styles.rowTop },
|
|
1765
|
+
el("strong", { className: styles.name, title: entry.moduleName }, moduleShortName(entry.moduleName)),
|
|
1766
|
+
el("span", { className: styles.tag, "data-enabled": entry.enabled ? "true" : "false" },
|
|
1767
|
+
t(entry.enabled ? "enabledTag" : "disabledTag")),
|
|
1768
|
+
entry.extra === true ? el("span", { className: styles.tag, "data-user": "true" }, t("extraTag")) : null,
|
|
1769
|
+
entry.protected ? el("span", { className: styles.tag, "data-user": "true" }, t("protectedTag")) : null,
|
|
1770
|
+
entry.userDisabled ? el("span", { className: styles.tag, "data-user": "true" }, t("userDisabledTag")) : null,
|
|
1771
|
+
entry.userForced ? el("span", { className: styles.tag, "data-user": "true" }, t("userForcedTag")) : null,
|
|
1772
|
+
),
|
|
1773
|
+
el("div", { className: styles.meta },
|
|
1774
|
+
el("span", { className: styles.phase, "data-phase": entry.fiberPhase ?? "unobserved" },
|
|
1775
|
+
phaseLabel(entry.fiberPhase, t)),
|
|
1776
|
+
el("code", null, entry.entryId),
|
|
1777
|
+
),
|
|
1778
|
+
el("div", { className: styles.rowTop, style: { justifyContent: "space-between" } },
|
|
1779
|
+
el("div", { className: styles.rowTop },
|
|
1780
|
+
el("button", {
|
|
1781
|
+
type: "button",
|
|
1782
|
+
className: styles.toggle,
|
|
1783
|
+
disabled: !entry.toggleable || busy === entry.entryId,
|
|
1784
|
+
onClick: () => toggle(entry, !entry.enabled),
|
|
1785
|
+
}, busy === entry.entryId ? t("loadingPhase") : t(entry.enabled ? "off" : "on")),
|
|
1786
|
+
el("button", {
|
|
1787
|
+
type: "button",
|
|
1788
|
+
className: styles.toggle,
|
|
1789
|
+
onClick: () => loadDetails(entry),
|
|
1790
|
+
}, t("details")),
|
|
1791
|
+
),
|
|
1792
|
+
el("div", { className: styles.rowTop },
|
|
1793
|
+
entry.extra === true && entry.rowId !== "plugin-console"
|
|
1794
|
+
? el("button", {
|
|
1795
|
+
type: "button",
|
|
1796
|
+
className: styles.trashBtn,
|
|
1797
|
+
title: t("deletePlugin"),
|
|
1798
|
+
"aria-label": t("deletePlugin"),
|
|
1799
|
+
disabled: deleteBusy === entry.entryId,
|
|
1800
|
+
onClick: () => {
|
|
1801
|
+
if (confirmDeleteRowId === entry.entryId) {
|
|
1802
|
+
setDeleteBusy(entry.entryId);
|
|
1803
|
+
call("/plugin-console/uninstall", { entryId: entry.entryId }).then(
|
|
1804
|
+
(data) => {
|
|
1805
|
+
setConfirmDeleteRowId(null);
|
|
1806
|
+
if (data && data.restart === true) {
|
|
1807
|
+
setMessage(t("deleteBundleNote") + ":" + (data.packageName ?? "") + (data.uninstallError ? "(包卸载警告:" + data.uninstallError + ")" : ""));
|
|
1808
|
+
setRestartHint(true);
|
|
1809
|
+
} else {
|
|
1810
|
+
setMessage(t("deleteNote") + ":" + (data.packageName ?? "") + (data.uninstallError ? "(包卸载警告:" + data.uninstallError + ")" : ""));
|
|
1811
|
+
setTimeout(refresh, 1500);
|
|
1812
|
+
}
|
|
1813
|
+
},
|
|
1814
|
+
(error) => setMessage(t("failed") + ":" + friendlyGithubError(error).message),
|
|
1815
|
+
).finally(() => setDeleteBusy(null));
|
|
1816
|
+
} else {
|
|
1817
|
+
setConfirmDeleteRowId(entry.entryId);
|
|
1818
|
+
window.setTimeout(() => setConfirmDeleteRowId((v) => (v === entry.entryId ? null : v)), 3000);
|
|
1819
|
+
}
|
|
1820
|
+
},
|
|
1821
|
+
}, deleteBusy === entry.entryId
|
|
1822
|
+
? t("deleting")
|
|
1823
|
+
: confirmDeleteRowId === entry.entryId
|
|
1824
|
+
? t("confirmDelete")
|
|
1825
|
+
: el("svg", { width: 14, height: 14, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" },
|
|
1826
|
+
el("polyline", { points: "3 6 5 6 21 6" }),
|
|
1827
|
+
el("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" }),
|
|
1828
|
+
el("line", { x1: "10", y1: "11", x2: "10", y2: "17" }),
|
|
1829
|
+
el("line", { x1: "14", y1: "11", x2: "14", y2: "17" })))
|
|
1830
|
+
: null,
|
|
1831
|
+
entry.extra === true && entry.installDate
|
|
1832
|
+
? el("span", { className: styles.date }, entry.installDate)
|
|
1833
|
+
: null,
|
|
1834
|
+
),
|
|
1835
|
+
),
|
|
1836
|
+
detailPanel,
|
|
1837
|
+
);
|
|
1838
|
+
})
|
|
1839
|
+
: [];
|
|
1840
|
+
const marketShown = market === null || market.status !== "ready"
|
|
1841
|
+
? []
|
|
1842
|
+
: officialOnly
|
|
1843
|
+
? market.data.filter((item) => item.official === true || item.aggregateInstallable === true)
|
|
1844
|
+
: market.data;
|
|
1845
|
+
const marketRows = market === null ? [] : market.status === "loading"
|
|
1846
|
+
? [el("p", { key: "m", className: styles.status }, searchSource === "github" ? t("marketLoading") : t("marketLoadingOther").replace("{source}", sourceDisplayName(searchSource)))]
|
|
1847
|
+
: market.status === "error"
|
|
1848
|
+
? [el("div", { key: "m", className: styles.detail },
|
|
1849
|
+
el("p", { className: styles.status, "data-error": "true" }, t("marketError") + ":" + market.error.message),
|
|
1850
|
+
el("button", { type: "button", className: styles.toggle, onClick: search }, t("retry")))]
|
|
1851
|
+
: market.data.length === 0
|
|
1852
|
+
? [el("p", { key: "m", className: styles.status }, searchSource === "gitee" ? t("giteeEmpty") : (mode === "skills" ? t("skillsEmpty") : t("marketEmpty")))]
|
|
1853
|
+
: marketShown.length === 0
|
|
1854
|
+
? [el("p", { key: "m", className: styles.status },
|
|
1855
|
+
officialOnly && market.data.some((item) => item.official === undefined || item.official === null)
|
|
1856
|
+
? t("officialChecking")
|
|
1857
|
+
: t("marketEmpty"))]
|
|
1858
|
+
: marketShown.map((item) => {
|
|
1859
|
+
const desc = item.description ?? "";
|
|
1860
|
+
const long = desc.length > 90;
|
|
1861
|
+
const open = expandedDescs[item.fullName] === true;
|
|
1862
|
+
const shown = long && !open ? desc.slice(0, 90) + "…" : desc;
|
|
1863
|
+
const installedMatch = state.status === "ready"
|
|
1864
|
+
? state.data.entries.find((entry) =>
|
|
1865
|
+
(entry.repository && entry.repository.includes(item.fullName.toLowerCase()))
|
|
1866
|
+
|| moduleShortName(entry.moduleName) === item.fullName.toLowerCase().split("/")[1])
|
|
1867
|
+
: null;
|
|
1868
|
+
return el("li", { key: item.fullName, className: styles.item },
|
|
1869
|
+
el("div", { className: styles.itemTop },
|
|
1870
|
+
el("a", { href: item.htmlUrl, target: "_blank", rel: "noreferrer" }, item.fullName),
|
|
1871
|
+
el("span", { className: styles.stars }, "★ " + item.stars),
|
|
1872
|
+
item.official === true
|
|
1873
|
+
? el("span", { className: styles.tag, "data-user": "true", title: t("officialTitle") }, t("officialBadge"))
|
|
1874
|
+
: item.aggregate === true
|
|
1875
|
+
? el("span", { className: styles.tag, title: t("aggregateTitle") }, t("aggregateBadge"))
|
|
1876
|
+
: null,
|
|
1877
|
+
item.hasSkill === true || item.skillTopics !== undefined
|
|
1878
|
+
? el("span", { className: styles.tag, "data-skill": "true", title: t("skillTitle") + (Array.isArray(item.skillTopics) && item.skillTopics.length > 0 ? "(" + item.skillTopics.join(" / ") + ")" : "") }, t("skillBadge"))
|
|
1879
|
+
: null,
|
|
1880
|
+
item.hasSuite === true
|
|
1881
|
+
? el("span", { className: styles.tag, "data-suite": "true", title: t("suiteTitle") }, t("suiteBadge"))
|
|
1882
|
+
: null,
|
|
1883
|
+
),
|
|
1884
|
+
desc ? (long
|
|
1885
|
+
? (open
|
|
1886
|
+
? el("div", { className: styles.descWrap },
|
|
1887
|
+
el("div", { className: styles.descTopbar },
|
|
1888
|
+
el("button", {
|
|
1889
|
+
type: "button",
|
|
1890
|
+
onClick: () => setExpandedDescs((prev) => ({ ...prev, [item.fullName]: false })),
|
|
1891
|
+
}, t("collapseDesc"))),
|
|
1892
|
+
el("p", { className: styles.desc }, desc),
|
|
1893
|
+
el("div", { className: styles.descTopbar },
|
|
1894
|
+
el("button", {
|
|
1895
|
+
type: "button",
|
|
1896
|
+
onClick: () => setExpandedDescs((prev) => ({ ...prev, [item.fullName]: false })),
|
|
1897
|
+
}, t("collapseDesc"))))
|
|
1898
|
+
: el("div", null,
|
|
1899
|
+
el("p", { className: styles.desc }, shown),
|
|
1900
|
+
el("button", {
|
|
1901
|
+
type: "button",
|
|
1902
|
+
onClick: () => setExpandedDescs((prev) => ({ ...prev, [item.fullName]: true })),
|
|
1903
|
+
}, t("expandDesc"))))
|
|
1904
|
+
: el("p", { className: styles.desc }, desc))
|
|
1905
|
+
: null,
|
|
1906
|
+
el("div", { className: styles.rowTop },
|
|
1907
|
+
el("button", {
|
|
1908
|
+
type: "button",
|
|
1909
|
+
disabled: repoInfo !== null && repoInfo.status === "loading" && repoInfo.repo === item.fullName,
|
|
1910
|
+
onClick: () => inspect(item),
|
|
1911
|
+
}, t("view")),
|
|
1912
|
+
// 技能条目(技能市场模式 / 技能索引数据):走技能安装通道(clone → ~/.dsh/skills)
|
|
1913
|
+
item.skillTopics !== undefined
|
|
1914
|
+
? (() => {
|
|
1915
|
+
const skillName = item.fullName.split("/")[1];
|
|
1916
|
+
const already = installedSkills.some((s) => s.name === skillName);
|
|
1917
|
+
return el("button", {
|
|
1918
|
+
type: "button",
|
|
1919
|
+
className: styles.toggle,
|
|
1920
|
+
disabled: installing === item.fullName || already,
|
|
1921
|
+
title: already ? t("skillUninstallHint") : undefined,
|
|
1922
|
+
onClick: () => startSkillJob(item),
|
|
1923
|
+
}, installing === item.fullName ? t("installingLocal") : (already ? t("skillInstalledTag") : t("installSkill")));
|
|
1924
|
+
})()
|
|
1925
|
+
: item.fullName === "deepseek-ai/deepseek-harness"
|
|
1926
|
+
? (updateMap[item.fullName] !== undefined
|
|
1927
|
+
? (confirmFrameworkUpgrade
|
|
1928
|
+
? el("button", {
|
|
1929
|
+
type: "button",
|
|
1930
|
+
className: styles.toggle,
|
|
1931
|
+
disabled: frameworkUpgrading,
|
|
1932
|
+
onClick: doFrameworkUpgrade,
|
|
1933
|
+
}, frameworkUpgrading ? t("installingLocal") : t("frameworkUpgradeConfirm"))
|
|
1934
|
+
: el("button", {
|
|
1935
|
+
type: "button",
|
|
1936
|
+
className: styles.toggle,
|
|
1937
|
+
disabled: frameworkUpgrading,
|
|
1938
|
+
title: t("frameworkUpgradeTitle"),
|
|
1939
|
+
onClick: () => setConfirmFrameworkUpgrade(true),
|
|
1940
|
+
}, t("frameworkUpgradeBtn") + " → v" + updateMap[item.fullName]))
|
|
1941
|
+
: el("button", {
|
|
1942
|
+
type: "button",
|
|
1943
|
+
className: styles.toggle,
|
|
1944
|
+
disabled: true,
|
|
1945
|
+
title: t("frameworkUpToDateTitle"),
|
|
1946
|
+
}, t("frameworkUpToDate")))
|
|
1947
|
+
: updateMap[item.fullName] !== undefined
|
|
1948
|
+
? el("button", {
|
|
1949
|
+
type: "button",
|
|
1950
|
+
className: styles.toggle,
|
|
1951
|
+
disabled: installing === item.fullName,
|
|
1952
|
+
onClick: () => startJob("", installedMatch.moduleName),
|
|
1953
|
+
}, installing === item.fullName ? t("installingLocal") : t("update") + " → v" + updateMap[item.fullName])
|
|
1954
|
+
: el("button", {
|
|
1955
|
+
type: "button",
|
|
1956
|
+
className: styles.toggle,
|
|
1957
|
+
disabled: installing === item.fullName,
|
|
1958
|
+
onClick: () => addLocal(item),
|
|
1959
|
+
}, installing === item.fullName ? t("installingLocal") : (installedMatch !== null && installedMatch !== undefined ? t("update") : t("addLocal"))),
|
|
1960
|
+
),
|
|
1961
|
+
);
|
|
1962
|
+
});
|
|
1963
|
+
let detail = null;
|
|
1964
|
+
if (repoInfo !== null) {
|
|
1965
|
+
if (repoInfo.status === "loading") {
|
|
1966
|
+
detail = el("div", { className: styles.detail }, el("p", { className: styles.status }, t("repoLoading")));
|
|
1967
|
+
} else if (repoInfo.status === "error") {
|
|
1968
|
+
detail = el("div", { className: styles.detail },
|
|
1969
|
+
el("p", { className: styles.status, "data-error": "true" }, t("repoError") + ":" + repoInfo.error.message));
|
|
1970
|
+
} else {
|
|
1971
|
+
const info = repoInfo.data;
|
|
1972
|
+
const detailSkillName = info.skill?.name ?? info.skillName ?? repoInfo.repo.split("/")[1];
|
|
1973
|
+
const detailSkill = installedSkills.find((s) => s.name === detailSkillName) ?? null;
|
|
1974
|
+
const detailSkillInstalled = detailSkill !== null;
|
|
1975
|
+
const detailSkillDisabled = detailSkill?.disabled === true;
|
|
1976
|
+
detail = el("div", { className: styles.detail },
|
|
1977
|
+
el("div", { className: styles.rowTop },
|
|
1978
|
+
el("strong", { className: styles.name }, repoInfo.repo),
|
|
1979
|
+
info.dshHint ? el("span", { className: styles.tag, "data-user": "true" }, t("dshHint")) : null,
|
|
1980
|
+
info.hasSkill === true ? el("span", { className: styles.tag, "data-skill": "true", title: t("skillTitle") }, t("skillBadge")) : null,
|
|
1981
|
+
info.hasSuite === true ? el("span", { className: styles.tag, "data-suite": "true", title: t("suiteTitle") }, t("suiteBadge")) : null,
|
|
1982
|
+
),
|
|
1983
|
+
info.description ? el("p", { className: styles.desc }, info.description) : null,
|
|
1984
|
+
info.hasSkill === true && info.skill !== null && (info.skill.name || info.skill.description || info.skill.whenToUse)
|
|
1985
|
+
? el("div", null,
|
|
1986
|
+
el("p", { className: styles.status }, t("skillMetaLabel") + ":"),
|
|
1987
|
+
info.skill.name ? el("p", { className: styles.status }, t("skillNameLabel") + ":" + info.skill.name) : null,
|
|
1988
|
+
info.skill.description ? el("p", { className: styles.desc }, info.skill.description) : null,
|
|
1989
|
+
info.skill.whenToUse ? el("p", { className: styles.status }, t("skillWhenToUseLabel") + ":" + info.skill.whenToUse) : null,
|
|
1990
|
+
Array.isArray(info.skillTopics) && info.skillTopics.length > 0
|
|
1991
|
+
? el("p", { className: styles.status }, t("skillTopicsLabel") + ":" + info.skillTopics.join(" / "))
|
|
1992
|
+
: null,
|
|
1993
|
+
)
|
|
1994
|
+
: null,
|
|
1995
|
+
info.readme !== null && (info.readme.title || info.readme.summary)
|
|
1996
|
+
? el("div", null,
|
|
1997
|
+
el("p", { className: styles.status }, t("readmeLabel") + ":"),
|
|
1998
|
+
info.readme.title ? el("strong", null, info.readme.title + "。") : null,
|
|
1999
|
+
info.readme.summary ? el("p", { className: styles.desc }, info.readme.summary) : null,
|
|
2000
|
+
)
|
|
2001
|
+
: null,
|
|
2002
|
+
el("p", { className: styles.status },
|
|
2003
|
+
info.hasSuite === true && info.hasPackageJson === false
|
|
2004
|
+
? t("suiteRepoNote")
|
|
2005
|
+
: (info.hasSkill === true && info.hasPackageJson === false
|
|
2006
|
+
? t("skillRepoNote")
|
|
2007
|
+
: (t("packageName") + ":" + (info.packageName ?? t("noPackage"))))),
|
|
2008
|
+
info.installCommand
|
|
2009
|
+
? el("div", null,
|
|
2010
|
+
el("p", { className: styles.status }, t("officialInstallLabel") + ":"),
|
|
2011
|
+
el("pre", { className: styles.cmdBlock, style: { margin: "4px 0", padding: "8px 10px", background: "var(--dsw-alias-bg-layer-2)", border: "1px solid var(--dsw-alias-border-l2)", borderRadius: "8px", overflowX: "auto", fontSize: "12px", lineHeight: "18px", whiteSpace: "pre-wrap", wordBreak: "break-all" } }, info.installCommand),
|
|
2012
|
+
el("button", { type: "button", className: styles.toggle, onClick: () => copyInstallCommand(info.installCommand) }, t("copyInstallCmd")))
|
|
2013
|
+
: null,
|
|
2014
|
+
info.packageName !== null && info.packageName !== undefined && state.status === "ready"
|
|
2015
|
+
? (() => {
|
|
2016
|
+
const entry = state.data.entries.find((candidate) => candidate.moduleName === info.packageName);
|
|
2017
|
+
return entry ? el("p", { className: styles.status },
|
|
2018
|
+
t("installedAt") + ":" + (entry.installDate ?? "?") + (entry.version ? " · v" + entry.version : "")) : null;
|
|
2019
|
+
})()
|
|
2020
|
+
: null,
|
|
2021
|
+
info.hasSkill === true && detailSkillInstalled
|
|
2022
|
+
? el("p", { className: styles.status },
|
|
2023
|
+
t("skillDetailInstalled") + ":" + detailSkillName
|
|
2024
|
+
+ (detailSkillDisabled ? " · " + t("skillDetailDisabled") : "")
|
|
2025
|
+
+ (detailSkill.path ? " · " + t("skillDetailPath") + " " + detailSkill.path : ""))
|
|
2026
|
+
: null,
|
|
2027
|
+
info.packageName ? el("button", {
|
|
2028
|
+
type: "button",
|
|
2029
|
+
className: styles.toggle,
|
|
2030
|
+
disabled: installing === repoInfo.repo,
|
|
2031
|
+
onClick: () => startJob(repoInfo.repo, info.packageName),
|
|
2032
|
+
}, installing === repoInfo.repo ? t("installing") : (state.status === "ready" && state.data.entries.some((candidate) => candidate.moduleName === info.packageName) ? t("update") : t("install"))) : null,
|
|
2033
|
+
info.hasSkill === true ? el("button", {
|
|
2034
|
+
type: "button",
|
|
2035
|
+
className: styles.toggle,
|
|
2036
|
+
disabled: installing === repoInfo.repo || detailSkillInstalled,
|
|
2037
|
+
title: detailSkillInstalled ? t("skillUninstallHint") : undefined,
|
|
2038
|
+
onClick: () => startSkillJob({ fullName: repoInfo.repo, source: repoInfo.source ?? "github" }),
|
|
2039
|
+
}, installing === repoInfo.repo ? t("installing") : (detailSkillInstalled ? t("skillInstalledTag") : t("installSkill"))) : null,
|
|
2040
|
+
info.hasSuite === true && info.hasPackageJson === false ? el("button", {
|
|
2041
|
+
type: "button",
|
|
2042
|
+
className: styles.toggle,
|
|
2043
|
+
disabled: installing === repoInfo.repo,
|
|
2044
|
+
title: t("suiteTitle"),
|
|
2045
|
+
onClick: () => startSuiteJob({ fullName: repoInfo.repo, source: repoInfo.source ?? "github" }),
|
|
2046
|
+
}, installing === repoInfo.repo ? t("installing") : t("suiteInstall")) : null,
|
|
2047
|
+
info.hasSkill === true && detailSkillInstalled && !detailSkill.system
|
|
2048
|
+
? el("button", {
|
|
2049
|
+
type: "button",
|
|
2050
|
+
className: styles.toggle,
|
|
2051
|
+
disabled: togglingSkill === detailSkillName,
|
|
2052
|
+
onClick: () => doToggleSkill(detailSkillName, detailSkillDisabled),
|
|
2053
|
+
}, togglingSkill === detailSkillName ? t("installingLocal") : (detailSkillDisabled ? t("skillEnable") : t("skillDisable")))
|
|
2054
|
+
: null,
|
|
2055
|
+
info.privateRoot ? el("div", null,
|
|
2056
|
+
el("p", { className: styles.status, "data-error": "true" }, t("privateRootHint")),
|
|
2057
|
+
el("p", { className: styles.status }, t("subpackagesTitle") + ":"),
|
|
2058
|
+
subpackages === null ? null
|
|
2059
|
+
: subpackages.status === "loading"
|
|
2060
|
+
? el("p", { className: styles.status }, t("subpackagesLoading"))
|
|
2061
|
+
: subpackages.status === "error"
|
|
2062
|
+
? el("p", { className: styles.status, "data-error": "true" }, t("subpackagesError"))
|
|
2063
|
+
: subpackages.list.length === 0
|
|
2064
|
+
? el("p", { className: styles.status }, t("subpackagesEmpty"))
|
|
2065
|
+
: el("ul", { className: styles.market }, subpackages.list.map((sub) =>
|
|
2066
|
+
el("li", { key: sub.name, className: styles.item },
|
|
2067
|
+
el("div", { className: styles.itemTop },
|
|
2068
|
+
el("code", { className: styles.name }, sub.name)),
|
|
2069
|
+
el("div", { className: styles.rowTop },
|
|
2070
|
+
el("button", {
|
|
2071
|
+
type: "button",
|
|
2072
|
+
className: styles.toggle,
|
|
2073
|
+
disabled: installing === sub.name,
|
|
2074
|
+
onClick: () => startJob(repoInfo.repo, sub.name),
|
|
2075
|
+
}, installing === sub.name ? t("installing") : t("install")))))))
|
|
2076
|
+
: null,
|
|
2077
|
+
);
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
// 升级卡片显示条件:非 idle 且未被关闭。关闭语义——
|
|
2081
|
+
// 终态(done/failed)点叉号 = 永久关闭(localStorage 记录,刷新/重进不再出现);
|
|
2082
|
+
// 非终态(升级进行中)点叉号 = 仅本次会话隐藏,刷新/重进后仍恢复显示(保持开启卡片)
|
|
2083
|
+
const fwTerminalDismissed = frameworkStatus !== null
|
|
2084
|
+
&& (frameworkStatus.status === "done" || frameworkStatus.status === "failed")
|
|
2085
|
+
&& (() => { try { return localStorage.getItem("pc-fw-dismiss-terminal") === frameworkStatus.status; } catch { return false; } })();
|
|
2086
|
+
const fwShowCard = frameworkStatus !== null && frameworkStatus.status !== "idle" && !fwStatusDismissed && !fwTerminalDismissed;
|
|
2087
|
+
return el("section", { className: styles.section, "aria-busy": state.status === "loading" },
|
|
2088
|
+
state.status === "ready" && state.data.compat && !state.data.compat.supported
|
|
2089
|
+
? el("p", { className: styles.message, "data-error": "true" }, state.data.compat.notice)
|
|
2090
|
+
: null,
|
|
2091
|
+
state.status === "ready" && state.data.framework && state.data.framework.upgraded === true && !frameworkNoticeDismissed
|
|
2092
|
+
? el("div", { className: styles.messageRow },
|
|
2093
|
+
el("p", { className: styles.message }, t("frameworkUpgradeNotice").replace("{from}", state.data.framework.from ?? "?").replace("{to}", state.data.framework.version ?? "?").replace("{dir}", state.data.framework.backupDir ?? "").replace("{patch}", state.data.framework.patchApplied === true ? t("frameworkPatchApplied") : t("frameworkPatchSkipped") + (state.data.framework.patchNote ? ":" + state.data.framework.patchNote : ""))),
|
|
2094
|
+
el("button", { type: "button", className: styles.toggle, onClick: () => setFrameworkNoticeDismissed(true) }, t("closeModal")))
|
|
2095
|
+
: null,
|
|
2096
|
+
fwShowCard && frameworkStatus.status !== "done"
|
|
2097
|
+
? el("button", {
|
|
2098
|
+
type: "button",
|
|
2099
|
+
className: styles.relaunchFloat,
|
|
2100
|
+
title: t("relaunchTitle"),
|
|
2101
|
+
"aria-label": t("relaunchBtn"),
|
|
2102
|
+
onClick: () => {
|
|
2103
|
+
call("/plugin-console/framework-relaunch", {}).then(
|
|
2104
|
+
(data) => { if (data && data.ok === true) setMessage(t("relaunchDone")); },
|
|
2105
|
+
(error) => setMessage(t("failed") + ":" + friendlyGithubError(error).message),
|
|
2106
|
+
);
|
|
2107
|
+
},
|
|
2108
|
+
}, t("relaunchBtn"))
|
|
2109
|
+
: null,
|
|
2110
|
+
fwShowCard
|
|
2111
|
+
? el("div", { className: styles.detail },
|
|
2112
|
+
el("div", { className: styles.rowTop },
|
|
2113
|
+
el("strong", { className: styles.name }, t("frameworkUpgradeBtn") + " " + (frameworkStatus.status === "done" ? "✓" : (frameworkStatus.status === "failed" ? "✕" : t("installingLocal")))),
|
|
2114
|
+
el("button", { type: "button", className: styles.trashBtn, title: t("closeModal"), "aria-label": t("closeModal"), onClick: () => {
|
|
2115
|
+
// 终态关闭 = 永久(localStorage 持久化);非终态关闭 = 会话内临时隐藏
|
|
2116
|
+
if (frameworkStatus !== null && (frameworkStatus.status === "done" || frameworkStatus.status === "failed")) {
|
|
2117
|
+
try { localStorage.setItem("pc-fw-dismiss-terminal", frameworkStatus.status); } catch {}
|
|
2118
|
+
}
|
|
2119
|
+
setFwStatusDismissed(true);
|
|
2120
|
+
} }, "✕")),
|
|
2121
|
+
el("ul", { className: styles.market }, FW_STEPS.map((step, idx) => {
|
|
2122
|
+
const cur = FW_STEPS.findIndex((s) => s.key === frameworkStatus.status);
|
|
2123
|
+
const state2 = frameworkStatus.status === "done" ? "ok" : (frameworkStatus.status === "failed" ? "fail" : (idx < cur ? "ok" : (idx === cur ? "cur" : "wait")));
|
|
2124
|
+
return el("li", { key: step.key, className: styles.item },
|
|
2125
|
+
el("div", { className: styles.itemTop },
|
|
2126
|
+
el("code", { className: styles.name }, (state2 === "ok" ? "✓ " : state2 === "cur" ? "⟳ " : state2 === "fail" ? "✕ " : "· ") + step.label),
|
|
2127
|
+
state2 === "cur" ? el("span", { className: styles.spinner }) : null));
|
|
2128
|
+
})),
|
|
2129
|
+
frameworkStatus.message ? el("p", { className: styles.status, "data-error": frameworkStatus.status === "failed" ? "true" : undefined }, frameworkStatus.message) : null)
|
|
2130
|
+
: null,
|
|
2131
|
+
message !== null ? el("div", { className: styles.messageRow }, el("p", { className: styles.message }, message), reloadHint ? el("button", { type: "button", className: styles.toggle, onClick: () => window.location.reload() }, t("reloadPage")) : null, restartHint ? el("button", { type: "button", className: styles.toggle, onClick: () => { setMessage(t("restarting")); call("/plugin-console/restart", {}).then(() => { window.setTimeout(() => window.location.reload(), 12000); }, () => {}); } }, t("restartService")) : null) : null,
|
|
2132
|
+
el("div", { className: styles.marketHead },
|
|
2133
|
+
el("h3", null, mode === "skills"
|
|
2134
|
+
? (searchSource === "github"
|
|
2135
|
+
? t("skillsMarketTitle")
|
|
2136
|
+
: t("skillsMarketTitleOther").replace("{source}", sourceDisplayName(searchSource)))
|
|
2137
|
+
: (searchSource === "github"
|
|
2138
|
+
? t("marketTitle")
|
|
2139
|
+
: t("marketTitleOther").replace("{source}", sourceDisplayName(searchSource)))),
|
|
2140
|
+
el("div", { id: "pc-ghwrap", className: styles.ghwrap },
|
|
2141
|
+
el("button", {
|
|
2142
|
+
type: "button",
|
|
2143
|
+
className: styles.ghpill,
|
|
2144
|
+
"data-on": state.status === "ready" && state.data.github && state.data.github.loggedIn ? "true" : "false",
|
|
2145
|
+
title: t("searchSourceTitle"),
|
|
2146
|
+
"aria-label": t("searchSourceTitle"),
|
|
2147
|
+
onClick: () => setSourceMenuOpen((v) => !v),
|
|
2148
|
+
},
|
|
2149
|
+
searchSource === "github"
|
|
2150
|
+
? (state.status === "ready" && state.data.github && state.data.github.loggedIn
|
|
2151
|
+
? t("githubLoggedIn") + (state.data.github.login ?? "unknown")
|
|
2152
|
+
: t("githubCornerOut"))
|
|
2153
|
+
: t("sourceOf") + ((searchSourcesList !== null ? searchSourcesList.find((s) => s.id === searchSource)?.name : undefined)
|
|
2154
|
+
?? (searchSource === "gitee" ? "Gitee" : searchSource)),
|
|
2155
|
+
" ▾"),
|
|
2156
|
+
sourceMenuOpen
|
|
2157
|
+
? el("div", { className: styles.sourceMenu },
|
|
2158
|
+
(searchSourcesList !== null && searchSourcesList.length > 0
|
|
2159
|
+
? searchSourcesList
|
|
2160
|
+
: [{ id: "github", name: "GitHub" }, { id: "gitee", name: "Gitee" }]
|
|
2161
|
+
).map((s) =>
|
|
2162
|
+
el("button", {
|
|
2163
|
+
type: "button",
|
|
2164
|
+
key: s.id,
|
|
2165
|
+
className: styles.sourceOpt,
|
|
2166
|
+
"data-active": searchSource === s.id ? "true" : "false",
|
|
2167
|
+
onClick: () => {
|
|
2168
|
+
setSearchSource(s.id);
|
|
2169
|
+
try { localStorage.setItem("pc-search-source", s.id); } catch {}
|
|
2170
|
+
setSourceMenuOpen(false);
|
|
2171
|
+
},
|
|
2172
|
+
}, s.name)))
|
|
2173
|
+
: null,
|
|
2174
|
+
)),
|
|
2175
|
+
el("button", {
|
|
2176
|
+
type: "button",
|
|
2177
|
+
className: styles.restartBtn,
|
|
2178
|
+
title: t("restartService"),
|
|
2179
|
+
onClick: () => { setMessage(t("restarting")); call("/plugin-console/restart", {}).then(() => { window.setTimeout(() => window.location.reload(), 12000); }, () => {}); },
|
|
2180
|
+
}, t("restartService")),
|
|
2181
|
+
el("button", {
|
|
2182
|
+
type: "button",
|
|
2183
|
+
className: styles.aiToggle,
|
|
2184
|
+
"data-active": aiFallback ? "true" : "false",
|
|
2185
|
+
title: t("aiFallbackTitle"),
|
|
2186
|
+
"aria-label": t("aiFallbackLabel"),
|
|
2187
|
+
onClick: () => setAiFallback((v) => {
|
|
2188
|
+
const next = !v;
|
|
2189
|
+
try { localStorage.setItem("pc-ai-fallback-v2", next ? "on" : "off"); } catch {}
|
|
2190
|
+
return next;
|
|
2191
|
+
}),
|
|
2192
|
+
}, t("aiFallbackLabel")),
|
|
2193
|
+
el("button", {
|
|
2194
|
+
type: "button",
|
|
2195
|
+
className: styles.sourcesFloat,
|
|
2196
|
+
title: t("sourcesTitle"),
|
|
2197
|
+
"aria-label": t("sourcesBtn"),
|
|
2198
|
+
onClick: openSources,
|
|
2199
|
+
}, t("sourcesBtn")),
|
|
2200
|
+
el("button", {
|
|
2201
|
+
type: "button",
|
|
2202
|
+
className: styles.modeFloat,
|
|
2203
|
+
"data-mode": mode === "skills" ? "true" : "false",
|
|
2204
|
+
title: t("modeBtn"),
|
|
2205
|
+
"aria-label": t("modeBtn"),
|
|
2206
|
+
onClick: () => {
|
|
2207
|
+
const next = mode === "plugins" ? "skills" : "plugins";
|
|
2208
|
+
setMode(next);
|
|
2209
|
+
try { localStorage.setItem("pc-market-mode", next); } catch {}
|
|
2210
|
+
setMarket(null);
|
|
2211
|
+
},
|
|
2212
|
+
}, mode === "skills" ? t("pluginsMode") : t("skillsMode")),
|
|
2213
|
+
market !== null && market.status === "ready" && market.data.length > 0
|
|
2214
|
+
? el("div", { className: styles.backTopWrap },
|
|
2215
|
+
showBackTop
|
|
2216
|
+
? el("button", {
|
|
2217
|
+
type: "button",
|
|
2218
|
+
className: styles.backTop,
|
|
2219
|
+
title: t("backToSearch"),
|
|
2220
|
+
"aria-label": t("backToSearch"),
|
|
2221
|
+
onClick: () => {
|
|
2222
|
+
const node = document.getElementById("pc-market-search");
|
|
2223
|
+
if (node) node.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
2224
|
+
},
|
|
2225
|
+
}, el("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round" },
|
|
2226
|
+
el("polyline", { points: "18 15 12 9 6 15" })))
|
|
2227
|
+
: null,
|
|
2228
|
+
el("button", {
|
|
2229
|
+
type: "button",
|
|
2230
|
+
className: styles.backTop,
|
|
2231
|
+
title: marketCollapsed ? t("expandResults") : t("collapseResults"),
|
|
2232
|
+
"aria-label": marketCollapsed ? t("expandResults") : t("collapseResults"),
|
|
2233
|
+
onClick: () => setMarketCollapsed((v) => !v),
|
|
2234
|
+
}, el("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round" },
|
|
2235
|
+
marketCollapsed
|
|
2236
|
+
? [el("polyline", { key: "a", points: "6 9 12 15 18 9" }), el("polyline", { key: "b", points: "6 16 12 22 18 16" })]
|
|
2237
|
+
: [el("polyline", { key: "a", points: "18 15 12 9 6 15" }), el("polyline", { key: "b", points: "18 8 12 2 6 8" })])))
|
|
2238
|
+
: null,
|
|
2239
|
+
el("p", { className: styles.note }, mode === "skills"
|
|
2240
|
+
? (searchSource === "github"
|
|
2241
|
+
? t("skillsNote")
|
|
2242
|
+
: t("skillsGithubOnly"))
|
|
2243
|
+
: (searchSource === "github" ? t("direct") : t("directOther").replace("{source}", sourceDisplayName(searchSource)))),
|
|
2244
|
+
el("div", { className: styles.search, id: "pc-market-search" },
|
|
2245
|
+
el("div", { className: styles.searchInner },
|
|
2246
|
+
el("input", {
|
|
2247
|
+
type: "search",
|
|
2248
|
+
value: query,
|
|
2249
|
+
placeholder: mode === "skills"
|
|
2250
|
+
? (searchSource === "github"
|
|
2251
|
+
? t("skillsSearchPlaceholder")
|
|
2252
|
+
: t("skillsGithubOnly"))
|
|
2253
|
+
: (searchSource === "github"
|
|
2254
|
+
? t("searchPlaceholder")
|
|
2255
|
+
: searchSource === "gitee"
|
|
2256
|
+
? t("giteeRepoPlaceholder")
|
|
2257
|
+
: t("searchPlaceholderOther").replace("{source}", sourceDisplayName(searchSource))),
|
|
2258
|
+
"aria-label": t("search"),
|
|
2259
|
+
onChange: (event) => setQuery(event.currentTarget.value),
|
|
2260
|
+
onKeyDown: (event) => { if (event.key === "Enter") search(); },
|
|
2261
|
+
}),
|
|
2262
|
+
),
|
|
2263
|
+
mode === "plugins"
|
|
2264
|
+
? el("button", {
|
|
2265
|
+
type: "button",
|
|
2266
|
+
className: styles.starBtn,
|
|
2267
|
+
"data-active": officialOnly ? "true" : "false",
|
|
2268
|
+
title: t("officialTitle"),
|
|
2269
|
+
"aria-label": t("officialOnlyToggle"),
|
|
2270
|
+
onClick: () => setOfficialOnly((v) => !v),
|
|
2271
|
+
}, officialOnly ? "★" : "☆")
|
|
2272
|
+
: null,
|
|
2273
|
+
mode === "plugins"
|
|
2274
|
+
? el("button", {
|
|
2275
|
+
type: "button",
|
|
2276
|
+
className: styles.starBtn,
|
|
2277
|
+
"data-active": multiSource ? "true" : "false",
|
|
2278
|
+
title: t("multiSourceTitle"),
|
|
2279
|
+
"aria-label": t("multiSourceToggle"),
|
|
2280
|
+
onClick: () => setMultiSource((v) => {
|
|
2281
|
+
const next = !v;
|
|
2282
|
+
try { localStorage.setItem("pc-multi-source", next ? "1" : "0"); } catch {}
|
|
2283
|
+
return next;
|
|
2284
|
+
}),
|
|
2285
|
+
}, multiSource ? "⊞" : "⊟")
|
|
2286
|
+
: null,
|
|
2287
|
+
el("button", { type: "button", onClick: search }, t("search")),
|
|
2288
|
+
),
|
|
2289
|
+
!marketCollapsed && marketRows.length > 0 ? el("ul", { className: styles.market }, marketRows) : null,
|
|
2290
|
+
!marketCollapsed && market !== null && market.status === "ready" && market.data.length >= 20
|
|
2291
|
+
? el("div", { className: styles.descTopbar },
|
|
2292
|
+
el("button", {
|
|
2293
|
+
type: "button",
|
|
2294
|
+
className: styles.toggle,
|
|
2295
|
+
disabled: loadingMore,
|
|
2296
|
+
onClick: loadMore,
|
|
2297
|
+
}, loadingMore ? t("loadingMore") : t("loadMore")))
|
|
2298
|
+
: null,
|
|
2299
|
+
Object.values(jobs).filter((job) => job.status === "installing").map((job) => {
|
|
2300
|
+
if (job.stage === "ai-consent") {
|
|
2301
|
+
// 需要用户同意时由最上层模态框负责展示,列表内只留状态行;
|
|
2302
|
+
// 已关闭兜底或已勾选"不再提醒"时显示对应说明
|
|
2303
|
+
const autoNote = !aiFallback ? t("aiFallbackOff") : (aiRemember ? t("aiRememberNote") : t("consentWaiting"));
|
|
2304
|
+
return el("div", { key: job.jobId, className: styles.detail },
|
|
2305
|
+
el("div", { className: styles.rowTop },
|
|
2306
|
+
el("span", { className: styles.spinner }),
|
|
2307
|
+
el("strong", { className: styles.name }, t("installingLocal") + ":" + (job.packageName ?? job.repo))),
|
|
2308
|
+
el("p", { className: styles.message }, autoNote));
|
|
2309
|
+
}
|
|
2310
|
+
const stageKey = job.stage === "preparing" ? "stagePreparing" : job.stage === "configuring" ? "stageConfiguring" : job.stage === "repairing" ? "stageRepairing" : "stageInstalling";
|
|
2311
|
+
const elapsed = Math.max(0, Math.round((Date.now() - (job.startedAt ?? Date.now())) / 1000));
|
|
2312
|
+
return el("div", { key: job.jobId, className: styles.detail },
|
|
2313
|
+
el("div", { className: styles.rowTop },
|
|
2314
|
+
el("span", { className: styles.spinner }),
|
|
2315
|
+
el("strong", { className: styles.name }, t("installingLocal") + ":" + (job.packageName ?? job.repo))),
|
|
2316
|
+
el("p", { className: styles.status },
|
|
2317
|
+
t("stageLabel") + ":" + t(stageKey) + " · " + t("elapsed") + " " + elapsed + "s"));
|
|
2318
|
+
}),
|
|
2319
|
+
Object.entries(suiteReports).map(([jobId, report]) =>
|
|
2320
|
+
el("div", { key: "suite-" + jobId, className: styles.detail },
|
|
2321
|
+
el("p", { className: styles.status }, t("suiteComponentType") + ":"),
|
|
2322
|
+
el("ul", { className: styles.market }, report.map((r) =>
|
|
2323
|
+
el("li", { key: r.component, className: styles.item },
|
|
2324
|
+
el("div", { className: styles.itemTop },
|
|
2325
|
+
el("code", { className: styles.name }, r.component),
|
|
2326
|
+
r.ok
|
|
2327
|
+
? el("span", { className: styles.tag, "data-user": "true" }, t("suiteComponentOk"))
|
|
2328
|
+
: el("span", { className: styles.tag, "data-error": "true" }, t("suiteComponentFail"))),
|
|
2329
|
+
r.note ? el("p", { className: styles.desc }, r.note) : null))))),
|
|
2330
|
+
(state.status === "ready" ? state.data.recentFailures ?? [] : []).filter((job) => !dismissedFailures[job.jobId]).map((job) =>
|
|
2331
|
+
el("div", { key: "fail-" + job.jobId, className: styles.detail },
|
|
2332
|
+
el("div", { className: styles.rowTop },
|
|
2333
|
+
el("span", { className: styles.phase, "data-phase": "failed" }, t("recentFailures")),
|
|
2334
|
+
el("strong", { className: styles.name }, job.packageName ?? job.repo),
|
|
2335
|
+
el("button", {
|
|
2336
|
+
type: "button",
|
|
2337
|
+
className: styles.trashBtn,
|
|
2338
|
+
title: t("dismissFailure"),
|
|
2339
|
+
"aria-label": t("dismissFailure"),
|
|
2340
|
+
onClick: () => setDismissedFailures((prev) => ({ ...prev, [job.jobId]: true })),
|
|
2341
|
+
}, "✕")),
|
|
2342
|
+
el("p", { className: styles.message, "data-error": "true" }, job.error ?? ""),
|
|
2343
|
+
el("div", { className: styles.rowTop },
|
|
2344
|
+
el("button", { type: "button", className: styles.toggle, onClick: () => startJob(job.repo, job.packageName, job.source ?? "github") }, t("retry")))),
|
|
2345
|
+
),
|
|
2346
|
+
repoInfo !== null
|
|
2347
|
+
? el("aside", { className: styles.floatPanel },
|
|
2348
|
+
el("div", { className: styles.descTopbar },
|
|
2349
|
+
el("button", { type: "button", className: styles.toggle, onClick: () => setRepoInfo(null) }, "✕")),
|
|
2350
|
+
detail)
|
|
2351
|
+
: null,
|
|
2352
|
+
el("div", { id: "pc-installed-search-area" },
|
|
2353
|
+
el("div", { className: styles.installedHead },
|
|
2354
|
+
el("h3", null, mode === "skills" ? t("installedSkillsTitle") : t("installedTitle")),
|
|
2355
|
+
mode === "plugins"
|
|
2356
|
+
? el("button", { type: "button", className: styles.toggle, onClick: () => setInstalledSearchOpen((v) => !v) }, t("search"))
|
|
2357
|
+
: null),
|
|
2358
|
+
mode === "plugins" && installedSearchOpen
|
|
2359
|
+
? el("div", { className: styles.search },
|
|
2360
|
+
el("input", {
|
|
2361
|
+
type: "search",
|
|
2362
|
+
id: "pc-installed-search-input",
|
|
2363
|
+
value: installedQuery,
|
|
2364
|
+
placeholder: t("searchInstalledPlaceholder"),
|
|
2365
|
+
"aria-label": t("searchInstalledPlaceholder"),
|
|
2366
|
+
onChange: (event) => setInstalledQuery(event.currentTarget.value),
|
|
2367
|
+
}),
|
|
2368
|
+
el("button", {
|
|
2369
|
+
type: "button",
|
|
2370
|
+
className: styles.extraBtn,
|
|
2371
|
+
"data-flash": extraFlash ? "true" : "false",
|
|
2372
|
+
title: t("extraFilterTitle"),
|
|
2373
|
+
"aria-label": t("extraFilterTitle"),
|
|
2374
|
+
onClick: () => {
|
|
2375
|
+
setExtraOnly((v) => !v);
|
|
2376
|
+
setExtraFlash(true);
|
|
2377
|
+
window.setTimeout(() => setExtraFlash(false), 1500);
|
|
2378
|
+
},
|
|
2379
|
+
}, extraOnly ? t("extraFilterAll") : t("extraFilter") + " · " + extraCount),
|
|
2380
|
+
)
|
|
2381
|
+
: null),
|
|
2382
|
+
mode === "skills"
|
|
2383
|
+
? (installedSkills.length === 0
|
|
2384
|
+
? el("p", { className: styles.status }, t("skillsEmpty"))
|
|
2385
|
+
: el("ul", { className: styles.list }, installedSkills.map((sk) =>
|
|
2386
|
+
el("li", { key: sk.name, className: styles.row },
|
|
2387
|
+
el("div", { className: styles.rowTop },
|
|
2388
|
+
el("code", { className: styles.name }, sk.name),
|
|
2389
|
+
sk.system === true
|
|
2390
|
+
? el("span", { className: styles.tag, "data-system": "true", title: t("skillSystemTitle") }, t("skillSystemTag"))
|
|
2391
|
+
: sk.disabled === true
|
|
2392
|
+
? el("span", { className: styles.tag, "data-disabled": "true", title: t("skillDisabledTitle") }, t("skillDisabledTag"))
|
|
2393
|
+
: el("span", { className: styles.tag, "data-skill": "true", title: t("skillTitle") }, t("skillBadge"))),
|
|
2394
|
+
el("div", { className: styles.meta }, sk.path ?? ""),
|
|
2395
|
+
el("div", { className: styles.rowTop },
|
|
2396
|
+
sk.system === true
|
|
2397
|
+
? null
|
|
2398
|
+
: el("button", {
|
|
2399
|
+
type: "button",
|
|
2400
|
+
className: styles.toggle,
|
|
2401
|
+
disabled: togglingSkill === sk.name,
|
|
2402
|
+
onClick: () => doToggleSkill(sk.name, sk.disabled === true),
|
|
2403
|
+
}, togglingSkill === sk.name ? t("installingLocal") : (sk.disabled === true ? t("skillEnable") : t("skillDisable"))),
|
|
2404
|
+
sk.system === true
|
|
2405
|
+
? null
|
|
2406
|
+
: confirmSkillDelete === sk.name
|
|
2407
|
+
? el("button", {
|
|
2408
|
+
type: "button",
|
|
2409
|
+
className: styles.toggle,
|
|
2410
|
+
onClick: () => doRemoveSkill(sk.name),
|
|
2411
|
+
}, t("skillDeleteConfirm").replace("{name}", sk.name))
|
|
2412
|
+
: el("button", {
|
|
2413
|
+
type: "button",
|
|
2414
|
+
className: styles.trashBtn,
|
|
2415
|
+
title: t("skillDelete"),
|
|
2416
|
+
"aria-label": t("skillDelete"),
|
|
2417
|
+
onClick: () => setConfirmSkillDelete(sk.name),
|
|
2418
|
+
}, "✕"))))))
|
|
2419
|
+
: (state.status === "loading"
|
|
2420
|
+
? el("p", { className: styles.status }, t("loading"))
|
|
2421
|
+
: state.status === "error"
|
|
2422
|
+
? el("div", { className: styles.detail },
|
|
2423
|
+
el("p", { className: styles.status, "data-error": "true" }, t("error")),
|
|
2424
|
+
el("button", { type: "button", className: styles.toggle, onClick: refresh }, t("retry")))
|
|
2425
|
+
: state.data.entries.length === 0
|
|
2426
|
+
? el("p", { className: styles.status }, t("empty"))
|
|
2427
|
+
: installedQueryNorm !== "" && rows.length === 0
|
|
2428
|
+
? el("p", { className: styles.status }, t("noMatch"))
|
|
2429
|
+
: extraOnly && rows.length === 0
|
|
2430
|
+
? el("p", { className: styles.status }, t("extraEmpty"))
|
|
2431
|
+
: el("ul", { className: styles.list }, rows)),
|
|
2432
|
+
showBackTop
|
|
2433
|
+
? el("button", {
|
|
2434
|
+
type: "button",
|
|
2435
|
+
className: styles.refresh,
|
|
2436
|
+
title: t("refreshList"),
|
|
2437
|
+
"aria-label": t("refreshList"),
|
|
2438
|
+
onClick: refresh,
|
|
2439
|
+
}, el("svg", { width: 14, height: 14, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round" },
|
|
2440
|
+
el("path", { d: "M21 12a9 9 0 1 1-2.64-6.36" }),
|
|
2441
|
+
el("polyline", { points: "21 3 21 9 15 9" })))
|
|
2442
|
+
: null,
|
|
2443
|
+
aiRemember
|
|
2444
|
+
? el("div", { className: styles.descTopbar },
|
|
2445
|
+
el("button", {
|
|
2446
|
+
type: "button",
|
|
2447
|
+
className: styles.toggle,
|
|
2448
|
+
onClick: () => {
|
|
2449
|
+
setAiRemember(false);
|
|
2450
|
+
try { localStorage.setItem("pc-ai-remember", "0"); } catch {}
|
|
2451
|
+
setMessage(t("aiRememberReset"));
|
|
2452
|
+
},
|
|
2453
|
+
}, t("aiRememberReset")))
|
|
2454
|
+
: null,
|
|
2455
|
+
(() => {
|
|
2456
|
+
// 最上层模态框:需要 AI 兜底授权时无论如何都让用户看到
|
|
2457
|
+
const consentJob = Object.values(jobs).find((job) => job.status === "installing" && job.stage === "ai-consent" && aiFallback && !aiRemember);
|
|
2458
|
+
return consentJob === undefined
|
|
2459
|
+
? null
|
|
2460
|
+
: el("div", { className: styles.modalBackdrop },
|
|
2461
|
+
el("div", { className: styles.modalCard },
|
|
2462
|
+
el("strong", { className: styles.name }, t("installingLocal") + ":" + (consentJob.packageName ?? consentJob.repo)),
|
|
2463
|
+
el("p", { className: styles.message }, t("aiConsentText")),
|
|
2464
|
+
el("label", { className: styles.consentRemember },
|
|
2465
|
+
el("input", {
|
|
2466
|
+
type: "checkbox",
|
|
2467
|
+
checked: aiRemember,
|
|
2468
|
+
onChange: (event) => {
|
|
2469
|
+
const checked = event.currentTarget.checked;
|
|
2470
|
+
setAiRemember(checked);
|
|
2471
|
+
try { localStorage.setItem("pc-ai-remember", checked ? "1" : "0"); } catch {}
|
|
2472
|
+
},
|
|
2473
|
+
}),
|
|
2474
|
+
t("aiConsentRemember")),
|
|
2475
|
+
el("div", { className: styles.rowTop },
|
|
2476
|
+
el("button", { type: "button", className: styles.toggle, onClick: () => { try { localStorage.setItem("pc-ai-remember", aiRemember ? "1" : "0"); } catch {} aiConsent(consentJob.jobId, true); } }, t("aiConsentApprove")),
|
|
2477
|
+
el("button", { type: "button", className: styles.toggle, onClick: () => aiConsent(consentJob.jobId, false) }, t("aiConsentDecline")))));
|
|
2478
|
+
})(),
|
|
2479
|
+
sourcesOpen
|
|
2480
|
+
? el("div", { className: styles.modalBackdrop },
|
|
2481
|
+
el("div", { className: styles.modalCard },
|
|
2482
|
+
el("div", { className: styles.rowTop, style: { justifyContent: "space-between" } },
|
|
2483
|
+
el("strong", { className: styles.name }, t("sourcesTitle")),
|
|
2484
|
+
el("button", { type: "button", className: styles.trashBtn, style: { fontSize: 14, padding: "4px 8px" }, title: t("closeModal"), "aria-label": t("closeModal"), onClick: () => setSourcesOpen(false) }, "✕")),
|
|
2485
|
+
el("p", { className: styles.message }, t("sourcesDesc")),
|
|
2486
|
+
sourcesData === null
|
|
2487
|
+
? el("p", { className: styles.status }, t("loading"))
|
|
2488
|
+
: el("div", { style: { flexDirection: "column", gap: "8px", display: "flex" } },
|
|
2489
|
+
sourcesData.registries.map((src) =>
|
|
2490
|
+
editReg !== null && editReg.id === src.id
|
|
2491
|
+
? el("div", { key: src.id, className: styles.rowTop, style: { justifyContent: "space-between" } },
|
|
2492
|
+
el("input", { type: "text", value: editReg.name, onChange: (event) => setEditReg({ ...editReg, name: event.currentTarget.value }), style: { flex: 1, minWidth: 80 } }),
|
|
2493
|
+
el("input", { type: "text", value: editReg.url, onChange: (event) => setEditReg({ ...editReg, url: event.currentTarget.value }), style: { flex: 2, minWidth: 120 } }),
|
|
2494
|
+
el("button", { type: "button", className: styles.toggle, disabled: sourcesBusy, onClick: saveEditReg }, t("saveSource")),
|
|
2495
|
+
el("button", { type: "button", className: styles.toggle, onClick: () => setEditReg(null) }, t("cancelEdit")))
|
|
2496
|
+
: el("div", { key: src.id, className: styles.rowTop, style: { justifyContent: "space-between" } },
|
|
2497
|
+
el("div", { className: styles.rowTop, style: { flex: 1, minWidth: 0 } },
|
|
2498
|
+
el("strong", { className: styles.name, style: { flex: "none", maxWidth: "35%" } }, src.name + (src.primary ? "(" + t("sourcePrimary") + ")" : "")),
|
|
2499
|
+
el("code", { className: styles.srcUrl }, src.url)),
|
|
2500
|
+
el("div", { className: styles.rowTop },
|
|
2501
|
+
src.primary ? null : el("button", { type: "button", className: styles.toggle, disabled: sourcesBusy, onClick: () => sourcesAction({ action: "set-primary", id: src.id }) }, t("setPrimary")),
|
|
2502
|
+
el("button", { type: "button", className: styles.toggle, disabled: sourcesBusy, onClick: () => setEditReg({ id: src.id, name: src.name, url: src.url }) }, t("editSource"))))),
|
|
2503
|
+
el("div", { className: styles.rowTop },
|
|
2504
|
+
el("input", { type: "text", placeholder: t("sourceName"), value: sourceName, onChange: (event) => setSourceName(event.currentTarget.value), style: { flex: 1, minWidth: 0 } }),
|
|
2505
|
+
el("input", { type: "text", placeholder: t("sourceUrl"), value: sourceUrl, onChange: (event) => setSourceUrl(event.currentTarget.value), style: { flex: 2, minWidth: 0 } }),
|
|
2506
|
+
el("button", { type: "button", className: styles.toggle, disabled: sourcesBusy, onClick: addSource }, t("addSource"))),
|
|
2507
|
+
el("strong", { className: styles.name }, t("searchSourcesTitle")),
|
|
2508
|
+
el("p", { className: styles.message }, t("searchSourcesDesc")),
|
|
2509
|
+
(sourcesData.searchSources ?? []).map((s) =>
|
|
2510
|
+
el("div", { key: s.id, className: styles.rowTop, style: { justifyContent: "space-between" } },
|
|
2511
|
+
el("div", { className: styles.rowTop, style: { flex: 1, minWidth: 0 } },
|
|
2512
|
+
el("strong", { className: styles.name, style: { flex: "none", maxWidth: "35%" } }, s.name),
|
|
2513
|
+
s.type === "custom" ? el("code", { className: styles.srcUrl }, s.url) : el("span", { className: styles.tag }, "内置"),
|
|
2514
|
+
s.type === "custom" && s.headers !== undefined && Object.keys(s.headers).length > 0
|
|
2515
|
+
? el("span", { className: styles.tag, title: t("headersPlaceholder") }, "🔒 " + Object.keys(s.headers).length)
|
|
2516
|
+
: null),
|
|
2517
|
+
s.type === "custom"
|
|
2518
|
+
? el("button", { type: "button", className: styles.toggle, disabled: sourcesBusy, onClick: () => sourcesAction({ action: "remove-search", id: s.id }) }, t("removeSource"))
|
|
2519
|
+
: null)),
|
|
2520
|
+
el("div", { className: styles.rowTop },
|
|
2521
|
+
el("input", { type: "text", placeholder: t("sourceName"), value: searchSourceName, onChange: (event) => setSearchSourceName(event.currentTarget.value), style: { flex: 1, minWidth: 0 } }),
|
|
2522
|
+
el("input", { type: "text", placeholder: t("searchUrlPlaceholder"), value: searchSourceUrl, onChange: (event) => setSearchSourceUrl(event.currentTarget.value), style: { flex: 2, minWidth: 0 } }),
|
|
2523
|
+
el("button", { type: "button", className: styles.toggle, disabled: sourcesBusy, onClick: addSearchSource }, t("addSearchSource"))),
|
|
2524
|
+
el("textarea", {
|
|
2525
|
+
rows: 2,
|
|
2526
|
+
placeholder: t("headersPlaceholder"),
|
|
2527
|
+
value: searchSourceHeaders,
|
|
2528
|
+
onChange: (event) => setSearchSourceHeaders(event.currentTarget.value),
|
|
2529
|
+
style: { width: "100%", boxSizing: "border-box", font: "inherit", resize: "vertical" },
|
|
2530
|
+
}),
|
|
2531
|
+
el("strong", { className: styles.name }, t("giteeTitle")),
|
|
2532
|
+
el("p", { className: styles.message }, t("giteeDesc").replace("{port}", String(window.location.port || "3080"))),
|
|
2533
|
+
el("div", { className: styles.rowTop, style: { justifyContent: "space-between" } },
|
|
2534
|
+
el("span", { className: styles.tag },
|
|
2535
|
+
giteeStatus !== null && giteeStatus.hasToken
|
|
2536
|
+
? t("giteeLoggedIn") + (giteeStatus.login || "—")
|
|
2537
|
+
: (giteeStatus !== null && giteeStatus.clientConfigured ? t("giteeSetupHint") : t("giteeLoginPrompt")))),
|
|
2538
|
+
el("div", { className: styles.rowTop },
|
|
2539
|
+
el("input", { type: "text", placeholder: t("giteeClientId"), value: giteeClientId, onChange: (event) => setGiteeClientId(event.currentTarget.value), style: { flex: 1, minWidth: 0 } }),
|
|
2540
|
+
el("input", { type: "password", placeholder: t("giteeClientSecret"), value: giteeClientSecret, onChange: (event) => setGiteeClientSecret(event.currentTarget.value), style: { flex: 2, minWidth: 0 } }),
|
|
2541
|
+
el("button", { type: "button", className: styles.toggle, disabled: sourcesBusy || !giteeClientId.trim() || !giteeClientSecret.trim(), onClick: saveGiteeSetup }, t("giteeSave"))),
|
|
2542
|
+
el("div", { className: styles.rowTop },
|
|
2543
|
+
el("button", { type: "button", className: styles.toggle, disabled: giteeStatus !== null && !giteeStatus.clientConfigured, onClick: startGiteeOauth }, t("giteeLoginBtn")),
|
|
2544
|
+
giteeStatus !== null && giteeStatus.hasToken
|
|
2545
|
+
? el("button", { type: "button", className: styles.toggle, disabled: sourcesBusy, onClick: () => sourcesAction({ action: "gitee-clear" }) }, t("giteeClear"))
|
|
2546
|
+
: null)),
|
|
2547
|
+
el("div", { className: styles.rowTop },
|
|
2548
|
+
el("button", { type: "button", className: styles.toggle, disabled: sourcesBusy, onClick: () => sourcesAction({ action: "reset" }) }, t("resetSources")),
|
|
2549
|
+
el("button", { type: "button", className: styles.toggle, onClick: () => setSourcesOpen(false) }, t("closeModal")))))
|
|
2550
|
+
: null,
|
|
2551
|
+
);
|
|
2552
|
+
}
|
|
2553
|
+
function apply(ctx) {
|
|
2554
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), "plugin-console: dictionaries");
|
|
2555
|
+
const t = ctx.locale.bind(NS);
|
|
2556
|
+
ctx.slots.inject("settings.plugins.tab", () => ctx.slots.register({
|
|
2557
|
+
name: "settings.plugins.tab",
|
|
2558
|
+
id: "console",
|
|
2559
|
+
order: 20,
|
|
2560
|
+
label: () => t("tab"),
|
|
2561
|
+
locale: NS,
|
|
2562
|
+
inject: () => ({}),
|
|
2563
|
+
}, PluginConsoleTab));
|
|
2564
|
+
}
|
|
2565
|
+
exports.NS = NS;
|
|
2566
|
+
exports.apply = apply;
|
|
2567
|
+
exports.inject = inject;
|
|
2568
|
+
return module.exports;
|
|
2569
|
+
}
|
|
2570
|
+
});
|