dsh-skill-hub 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +146 -33
- package/lib/client.js +3477 -436
- package/lib/client.js.map +1 -1
- package/lib/index.js +2408 -366
- package/lib/types/client/SkillHubSettingsCard.d.ts +5 -0
- package/lib/types/client/api-config-scope.d.ts +6 -0
- package/lib/types/client/api.d.ts +44 -2
- package/lib/types/client/grouping.d.ts +64 -0
- package/lib/types/client/locales.d.ts +155 -18
- package/lib/types/client/market-catalog.d.ts +14 -0
- package/lib/types/client/panel/DisabledRow.d.ts +12 -0
- package/lib/types/client/panel/GroupSummary.d.ts +10 -0
- package/lib/types/client/panel/MarketView.d.ts +11 -0
- package/lib/types/client/panel/ScenesView.d.ts +11 -0
- package/lib/types/client/panel/SkillDetailView.d.ts +31 -0
- package/lib/types/client/panel/SkillHubPanel.d.ts +11 -3
- package/lib/types/client/panel/SkillRow.d.ts +11 -0
- package/lib/types/client/panel/SourceStatusBadge.d.ts +18 -0
- package/lib/types/client/panel/SourcesView.d.ts +10 -0
- package/lib/types/client/panel/TagEditorView.d.ts +29 -0
- package/lib/types/client/panel/dialogs.d.ts +73 -0
- package/lib/types/client/panel/format.d.ts +14 -0
- package/lib/types/client/panel/useSkillHub.d.ts +183 -0
- package/lib/types/client/settings-card.d.ts +21 -4
- package/lib/types/client/settings-form.d.ts +2 -0
- package/lib/types/client/settings-nav-icon.d.ts +8 -0
- package/lib/types/index.d.ts +6 -0
- package/lib/types/protocol.d.ts +428 -7
- package/lib/types/provider.d.ts +6 -1
- package/lib/types/repo.d.ts +133 -0
- package/lib/types/routes.d.ts +9 -4
- package/lib/types/skillfs.d.ts +19 -14
- package/lib/types/stats.d.ts +17 -4
- package/lib/types/store.d.ts +112 -6
- package/lib/types/update.d.ts +20 -0
- package/package.json +5 -3
- package/src/client/SkillHubSettingsCard.tsx +61 -8
- package/src/client/api-config-scope.test.ts +79 -0
- package/src/client/api-config-scope.ts +28 -4
- package/src/client/api.ts +239 -9
- package/src/client/grouping.test.ts +109 -0
- package/src/client/grouping.ts +120 -0
- package/src/client/index.tsx +5 -2
- package/src/client/locales.ts +314 -40
- package/src/client/market-catalog.ts +22 -0
- package/src/client/panel/DisabledRow.tsx +32 -0
- package/src/client/panel/GroupSummary.tsx +27 -0
- package/src/client/panel/MarketView.tsx +173 -0
- package/src/client/panel/ScenesView.tsx +75 -0
- package/src/client/panel/SkillDetailView.tsx +95 -0
- package/src/client/panel/SkillHubPanel.tsx +251 -257
- package/src/client/panel/SkillRow.tsx +54 -0
- package/src/client/panel/SourceStatusBadge.tsx +61 -0
- package/src/client/panel/SourcesView.tsx +104 -0
- package/src/client/panel/TagEditorView.tsx +89 -0
- package/src/client/panel/dialogs.tsx +185 -0
- package/src/client/panel/format.ts +31 -0
- package/src/client/panel/panel.module.css +209 -45
- package/src/client/panel/useSkillHub.ts +797 -0
- package/src/client/settings-card.module.css +44 -28
- package/src/client/settings-card.tsx +64 -8
- package/src/client/settings-form.ts +12 -0
- package/src/client/settings-nav-icon.ts +42 -0
- package/src/index.ts +19 -15
- package/src/protocol.ts +460 -7
- package/src/provider.test.ts +2 -4
- package/src/provider.ts +18 -14
- package/src/repo.test.ts +212 -0
- package/src/repo.ts +528 -0
- package/src/routes.test.ts +564 -52
- package/src/routes.ts +999 -153
- package/src/skillfs.test.ts +98 -16
- package/src/skillfs.ts +68 -33
- package/src/stats.test.ts +52 -17
- package/src/stats.ts +55 -18
- package/src/store.test.ts +205 -1
- package/src/store.ts +527 -24
- package/src/update.test.ts +73 -0
- package/src/update.ts +136 -0
package/lib/client.js
CHANGED
|
@@ -5,6 +5,7 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
|
+
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
8
9
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
10
|
let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
|
|
10
11
|
//#region src/protocol.ts
|
|
@@ -18,10 +19,31 @@ window.__ModuleLoader__.load({
|
|
|
18
19
|
const SKILL_HUB_API = {
|
|
19
20
|
catalog: "/api/skill-hub/catalog",
|
|
20
21
|
skill: "/api/skill-hub/skill",
|
|
22
|
+
skillDelete: "/api/skill-hub/skill/delete",
|
|
21
23
|
toggle: "/api/skill-hub/toggle",
|
|
24
|
+
toggleBatch: "/api/skill-hub/toggle-batch",
|
|
22
25
|
create: "/api/skill-hub/create",
|
|
23
26
|
stats: "/api/skill-hub/stats",
|
|
24
|
-
config: "/api/skill-hub/config"
|
|
27
|
+
config: "/api/skill-hub/config",
|
|
28
|
+
market: "/api/skill-hub/market",
|
|
29
|
+
marketSource: "/api/skill-hub/market/source",
|
|
30
|
+
marketSourceDelete: "/api/skill-hub/market/source/delete",
|
|
31
|
+
marketSourceRef: "/api/skill-hub/market/source/ref",
|
|
32
|
+
marketCheck: "/api/skill-hub/market/check",
|
|
33
|
+
marketSync: "/api/skill-hub/market/source/sync",
|
|
34
|
+
repo: "/api/skill-hub/repo",
|
|
35
|
+
repoImport: "/api/skill-hub/repo/import",
|
|
36
|
+
update: "/api/skill-hub/update",
|
|
37
|
+
groups: "/api/skill-hub/groups",
|
|
38
|
+
tag: "/api/skill-hub/tag",
|
|
39
|
+
tagDelete: "/api/skill-hub/tag/delete",
|
|
40
|
+
tagMembers: "/api/skill-hub/tag/members",
|
|
41
|
+
sources: "/api/skill-hub/sources",
|
|
42
|
+
sourceCheck: "/api/skill-hub/sources/check",
|
|
43
|
+
sourceSync: "/api/skill-hub/sources/sync",
|
|
44
|
+
sourceDelete: "/api/skill-hub/sources/delete",
|
|
45
|
+
sourceRestore: "/api/skill-hub/sources/restore",
|
|
46
|
+
sourceTrashClear: "/api/skill-hub/sources/trash/clear"
|
|
25
47
|
};
|
|
26
48
|
//#endregion
|
|
27
49
|
//#region src/client/api.ts
|
|
@@ -47,18 +69,42 @@ window.__ModuleLoader__.load({
|
|
|
47
69
|
if (!response.ok) throw new SkillHubApiError(typeof body === "object" && body !== null && typeof body.error === "string" ? body.error : "HTTP " + response.status);
|
|
48
70
|
return body;
|
|
49
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Fetch with a hard timeout: a hung connection (proxy, stalled keep-alive,
|
|
74
|
+
* host wedged) must never leave the panel stuck on "loading" forever — it
|
|
75
|
+
* fails fast and surfaces the error banner instead.
|
|
76
|
+
*/
|
|
77
|
+
async function fetchWithTimeout(input, init, ms = 1e4) {
|
|
78
|
+
const controller = new AbortController();
|
|
79
|
+
const timer = setTimeout(() => controller.abort(), ms);
|
|
80
|
+
try {
|
|
81
|
+
return await fetch(input, {
|
|
82
|
+
...init,
|
|
83
|
+
signal: controller.signal
|
|
84
|
+
});
|
|
85
|
+
} finally {
|
|
86
|
+
clearTimeout(timer);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
50
89
|
/** The browser half's only data entry point. */
|
|
51
90
|
var SkillHubApi = class {
|
|
52
91
|
async catalog() {
|
|
53
|
-
return readJson(await
|
|
92
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.catalog));
|
|
54
93
|
}
|
|
55
94
|
async skill(name) {
|
|
56
|
-
|
|
57
|
-
|
|
95
|
+
return (await readJson(await fetchWithTimeout(SKILL_HUB_API.skill + "?name=" + encodeURIComponent(name)))).skill;
|
|
96
|
+
}
|
|
97
|
+
/** Move one writable skill into the restorable trash. */
|
|
98
|
+
async deleteSkill(name) {
|
|
99
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.skillDelete, {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: { "content-type": "application/json" },
|
|
102
|
+
body: JSON.stringify({ name })
|
|
103
|
+
}));
|
|
58
104
|
}
|
|
59
|
-
/** Toggle
|
|
105
|
+
/** Toggle one skill; resolves with the fresh catalog from the route. */
|
|
60
106
|
async toggle(name, enabled) {
|
|
61
|
-
return (await readJson(await
|
|
107
|
+
return (await readJson(await fetchWithTimeout(SKILL_HUB_API.toggle, {
|
|
62
108
|
method: "POST",
|
|
63
109
|
headers: { "content-type": "application/json" },
|
|
64
110
|
body: JSON.stringify({
|
|
@@ -68,37 +114,219 @@ window.__ModuleLoader__.load({
|
|
|
68
114
|
}))).catalog;
|
|
69
115
|
}
|
|
70
116
|
async create(payload) {
|
|
71
|
-
return readJson(await
|
|
117
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.create, {
|
|
72
118
|
method: "POST",
|
|
73
119
|
headers: { "content-type": "application/json" },
|
|
74
120
|
body: JSON.stringify(payload)
|
|
75
121
|
}));
|
|
76
122
|
}
|
|
77
123
|
async stats() {
|
|
78
|
-
return readJson(await
|
|
124
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.stats));
|
|
125
|
+
}
|
|
126
|
+
/** Toggle a whole group in one write; resolves with the fresh catalog + failures. */
|
|
127
|
+
async toggleBatch(names, enabled) {
|
|
128
|
+
const payload = {
|
|
129
|
+
names,
|
|
130
|
+
enabled
|
|
131
|
+
};
|
|
132
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.toggleBatch, {
|
|
133
|
+
method: "POST",
|
|
134
|
+
headers: { "content-type": "application/json" },
|
|
135
|
+
body: JSON.stringify(payload)
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
/** The user's added market sources (codex-style repo slugs). */
|
|
139
|
+
async market() {
|
|
140
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.market));
|
|
141
|
+
}
|
|
142
|
+
/** Add a market source repo; resolves with the fresh list. */
|
|
143
|
+
async addMarketSource(repo) {
|
|
144
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.marketSource, {
|
|
145
|
+
method: "POST",
|
|
146
|
+
headers: { "content-type": "application/json" },
|
|
147
|
+
body: JSON.stringify({ repo })
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
/** Remove a market source repo; resolves with the fresh list. */
|
|
151
|
+
async removeMarketSource(repo) {
|
|
152
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.marketSourceDelete, {
|
|
153
|
+
method: "POST",
|
|
154
|
+
headers: { "content-type": "application/json" },
|
|
155
|
+
body: JSON.stringify({ repo })
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
/** Check every market source for updates (throttled server-side). */
|
|
159
|
+
async marketCheck() {
|
|
160
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.marketCheck));
|
|
161
|
+
}
|
|
162
|
+
/** Align a market source to its pinned ref; resolves with tracked skills. */
|
|
163
|
+
async marketSync(repo) {
|
|
164
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.marketSync, {
|
|
165
|
+
method: "POST",
|
|
166
|
+
headers: { "content-type": "application/json" },
|
|
167
|
+
body: JSON.stringify({ repo })
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
/** Pin a market source to an explicit ref (branch picker). */
|
|
171
|
+
async setMarketSourceRef(repo, ref) {
|
|
172
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.marketSourceRef, {
|
|
173
|
+
method: "POST",
|
|
174
|
+
headers: { "content-type": "application/json" },
|
|
175
|
+
body: JSON.stringify({
|
|
176
|
+
repo,
|
|
177
|
+
ref
|
|
178
|
+
})
|
|
179
|
+
}));
|
|
180
|
+
}
|
|
181
|
+
/** Check the hub's own latest GitHub release. */
|
|
182
|
+
async updateCheck() {
|
|
183
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.update));
|
|
184
|
+
}
|
|
185
|
+
/** Discover importable skills in a GitHub repo. */
|
|
186
|
+
async repoDiscover(repo) {
|
|
187
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.repo + "?repo=" + encodeURIComponent(repo)));
|
|
188
|
+
}
|
|
189
|
+
/** Install selected repo skills into the user-dsh root (records the source). */
|
|
190
|
+
async repoImport(repo, paths, ref) {
|
|
191
|
+
const payload = {
|
|
192
|
+
repo,
|
|
193
|
+
paths,
|
|
194
|
+
...ref !== void 0 && ref !== "" ? { ref } : {}
|
|
195
|
+
};
|
|
196
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.repoImport, {
|
|
197
|
+
method: "POST",
|
|
198
|
+
headers: { "content-type": "application/json" },
|
|
199
|
+
body: JSON.stringify(payload)
|
|
200
|
+
}));
|
|
79
201
|
}
|
|
80
202
|
/** Read the hub's runtime config (effective values + saved overrides). */
|
|
81
203
|
async config() {
|
|
82
|
-
return readJson(await
|
|
204
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.config));
|
|
83
205
|
}
|
|
84
206
|
/** Patch the hub's runtime config (null clears a saved override). */
|
|
85
207
|
async saveConfig(patch) {
|
|
86
|
-
return readJson(await
|
|
208
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.config, {
|
|
87
209
|
method: "POST",
|
|
88
210
|
headers: { "content-type": "application/json" },
|
|
89
211
|
body: JSON.stringify(patch)
|
|
90
212
|
}));
|
|
91
213
|
}
|
|
214
|
+
/** 用户 tag 分组 + 系统集合组 + origin 映射。 */
|
|
215
|
+
async groups() {
|
|
216
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.groups));
|
|
217
|
+
}
|
|
218
|
+
/** 新建(无 id)或重命名(带 id)一个 tag 分组;返回全部 tag。 */
|
|
219
|
+
async saveTag(payload) {
|
|
220
|
+
return (await readJson(await fetchWithTimeout(SKILL_HUB_API.tag, {
|
|
221
|
+
method: "POST",
|
|
222
|
+
headers: { "content-type": "application/json" },
|
|
223
|
+
body: JSON.stringify(payload)
|
|
224
|
+
}))).tags;
|
|
225
|
+
}
|
|
226
|
+
/** 删除一个 tag 分组;返回全部 tag。 */
|
|
227
|
+
async deleteTag(id) {
|
|
228
|
+
return (await readJson(await fetchWithTimeout(SKILL_HUB_API.tagDelete, {
|
|
229
|
+
method: "POST",
|
|
230
|
+
headers: { "content-type": "application/json" },
|
|
231
|
+
body: JSON.stringify({ id })
|
|
232
|
+
}))).tags;
|
|
233
|
+
}
|
|
234
|
+
/** 直接设置某 tag 的完整成员列表;返回全部 tag。 */
|
|
235
|
+
async setTagMembers(id, skillNames) {
|
|
236
|
+
return (await readJson(await fetchWithTimeout(SKILL_HUB_API.tagMembers, {
|
|
237
|
+
method: "POST",
|
|
238
|
+
headers: { "content-type": "application/json" },
|
|
239
|
+
body: JSON.stringify({
|
|
240
|
+
id,
|
|
241
|
+
skillNames
|
|
242
|
+
})
|
|
243
|
+
}))).tags;
|
|
244
|
+
}
|
|
245
|
+
/** 来源列表 + 派生 origin 映射 + 集合组 + 回收站。 */
|
|
246
|
+
async sources() {
|
|
247
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.sources));
|
|
248
|
+
}
|
|
249
|
+
/** 检查指定(或全部)来源的上游更新。 */
|
|
250
|
+
async checkSources(repo) {
|
|
251
|
+
const payload = repo !== void 0 ? { repo } : {};
|
|
252
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.sourceCheck, {
|
|
253
|
+
method: "POST",
|
|
254
|
+
headers: { "content-type": "application/json" },
|
|
255
|
+
body: JSON.stringify(payload)
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
258
|
+
/** 同步一个来源的所选(或全部)技能到上游最新版本。 */
|
|
259
|
+
async syncSource(repo, skills) {
|
|
260
|
+
const payload = skills !== void 0 ? {
|
|
261
|
+
repo,
|
|
262
|
+
skills
|
|
263
|
+
} : { repo };
|
|
264
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.sourceSync, {
|
|
265
|
+
method: "POST",
|
|
266
|
+
headers: { "content-type": "application/json" },
|
|
267
|
+
body: JSON.stringify(payload)
|
|
268
|
+
}));
|
|
269
|
+
}
|
|
270
|
+
/** 跟进上游删除:把所选技能移入回收站。 */
|
|
271
|
+
async confirmDeleteSource(repo, skills) {
|
|
272
|
+
const payload = {
|
|
273
|
+
repo,
|
|
274
|
+
skills
|
|
275
|
+
};
|
|
276
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.sourceDelete, {
|
|
277
|
+
method: "POST",
|
|
278
|
+
headers: { "content-type": "application/json" },
|
|
279
|
+
body: JSON.stringify(payload)
|
|
280
|
+
}));
|
|
281
|
+
}
|
|
282
|
+
/** 从回收站恢复一个技能。 */
|
|
283
|
+
async restoreSource(name) {
|
|
284
|
+
const payload = { name };
|
|
285
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.sourceRestore, {
|
|
286
|
+
method: "POST",
|
|
287
|
+
headers: { "content-type": "application/json" },
|
|
288
|
+
body: JSON.stringify(payload)
|
|
289
|
+
}));
|
|
290
|
+
}
|
|
291
|
+
/** Permanently delete every skill currently in the trash. */
|
|
292
|
+
async clearTrash() {
|
|
293
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.sourceTrashClear, { method: "POST" }));
|
|
294
|
+
}
|
|
92
295
|
};
|
|
93
296
|
//#endregion
|
|
94
297
|
//#region src/client/api-config-scope.ts
|
|
298
|
+
/** Model-invocable dot color default. Single source for the TS side; the
|
|
299
|
+
* panel's CSS mirrors it via --hub-model (panel.module.css). */
|
|
300
|
+
const DEFAULT_DOT_MODEL_COLOR = "#2f81f7";
|
|
301
|
+
/** User-invocable dot color default. Single source for the TS side; the
|
|
302
|
+
* panel's CSS mirrors it via --hub-user (panel.module.css). */
|
|
303
|
+
const DEFAULT_DOT_USER_COLOR = "#3fb950";
|
|
95
304
|
/** Built-in defaults every field inherits until the user overrides it. */
|
|
96
305
|
const DEFAULTS = {
|
|
97
306
|
enabled: true,
|
|
98
|
-
announceToAgent: true
|
|
307
|
+
announceToAgent: true,
|
|
308
|
+
showUseCount: true,
|
|
309
|
+
showUseTime: true,
|
|
310
|
+
showGroupSummary: true
|
|
99
311
|
};
|
|
312
|
+
/** Boolean config fields (color fields are strings). */
|
|
313
|
+
const BOOLEAN_FIELDS = /* @__PURE__ */ new Set([
|
|
314
|
+
"enabled",
|
|
315
|
+
"announceToAgent",
|
|
316
|
+
"showUseCount",
|
|
317
|
+
"showUseTime",
|
|
318
|
+
"showGroupSummary"
|
|
319
|
+
]);
|
|
100
320
|
/** Config fields the card edits (used to guard set/unset field names). */
|
|
101
|
-
const FIELDS = /* @__PURE__ */ new Set([
|
|
321
|
+
const FIELDS = /* @__PURE__ */ new Set([
|
|
322
|
+
"enabled",
|
|
323
|
+
"announceToAgent",
|
|
324
|
+
"dotModelColor",
|
|
325
|
+
"dotUserColor",
|
|
326
|
+
"showUseCount",
|
|
327
|
+
"showUseTime",
|
|
328
|
+
"showGroupSummary"
|
|
329
|
+
]);
|
|
102
330
|
/** FormScope-compatible reactive config handle (see module doc). */
|
|
103
331
|
var ApiConfigScope = class {
|
|
104
332
|
api;
|
|
@@ -140,7 +368,8 @@ window.__ModuleLoader__.load({
|
|
|
140
368
|
/** Write one field through the config route and adopt the persisted state. */
|
|
141
369
|
async set(field, value) {
|
|
142
370
|
if (!FIELDS.has(field)) return;
|
|
143
|
-
|
|
371
|
+
const patch = BOOLEAN_FIELDS.has(field) ? { [field]: Boolean(value) } : { [field]: typeof value === "string" ? value : void 0 };
|
|
372
|
+
await this.apply(patch);
|
|
144
373
|
}
|
|
145
374
|
/** Clear one field's override so it re-inherits the default. */
|
|
146
375
|
async unset(field) {
|
|
@@ -169,13 +398,20 @@ window.__ModuleLoader__.load({
|
|
|
169
398
|
*/
|
|
170
399
|
const zh = {
|
|
171
400
|
"entry.label": "技能",
|
|
172
|
-
"entry.tooltip": "
|
|
173
|
-
"view.
|
|
174
|
-
"view.
|
|
401
|
+
"entry.tooltip": "技能中枢:浏览、分组与同步管理本地技能",
|
|
402
|
+
"view.sources": "来源",
|
|
403
|
+
"view.scenes": "场景",
|
|
404
|
+
"view.market": "市场",
|
|
405
|
+
"view.flat": "平铺",
|
|
406
|
+
"view.grouped": "分组",
|
|
175
407
|
"panel.title": "技能中枢",
|
|
176
|
-
"panel.
|
|
408
|
+
"panel.count": "共 {count} 个技能",
|
|
409
|
+
"panel.disabledCount": "已禁用 {count}",
|
|
177
410
|
"panel.new": "新建技能",
|
|
178
411
|
"panel.search": "搜索技能…",
|
|
412
|
+
"legend.model": "模型可调 — Agent 可自动调用",
|
|
413
|
+
"legend.user": "用户可调 — 仅显式点名时触发",
|
|
414
|
+
"legend.hint": "圆点标在技能名旁;无圆点表示该技能不可被调用。",
|
|
179
415
|
"panel.enabled": "已启用",
|
|
180
416
|
"panel.disabled": "已禁用",
|
|
181
417
|
"panel.diagnostics": "发现诊断",
|
|
@@ -183,6 +419,65 @@ window.__ModuleLoader__.load({
|
|
|
183
419
|
"panel.emptyAll": "本地还没有技能。点击「新建技能」创建第一个。",
|
|
184
420
|
"panel.loading": "读取中…",
|
|
185
421
|
"panel.incomplete": "(目录可能不完整)",
|
|
422
|
+
"market.addHint": "像 codex 一样添加仓库源:输入 owner/repo 或 GitHub 链接,扫描其中的技能并一键导入;导入后自动跟踪上游更新。",
|
|
423
|
+
"market.addPlaceholder": "owner/repo 或 https://github.com/…",
|
|
424
|
+
"market.addSource": "添加源",
|
|
425
|
+
"market.noSources": "还没有市场源。从下方内置市场添加,或输入一个 GitHub 仓库。",
|
|
426
|
+
"market.catalogTitle": "内置市场",
|
|
427
|
+
"market.catalogHint": "精选技能仓库,添加后即可扫描安装,并持续检查更新。",
|
|
428
|
+
"market.catalog.anthropics": "Anthropic 官方技能合集(Claude Skills)",
|
|
429
|
+
"market.catalog.superpowers": "Superpowers 社区技能合集",
|
|
430
|
+
"market.catalog.mattpocock": "Matt Pocock 技能集",
|
|
431
|
+
"market.catalog.openDesign": "设计技能合集(design-templates)",
|
|
432
|
+
"market.added": "已添加",
|
|
433
|
+
"market.add": "添加",
|
|
434
|
+
"market.mySources": "我的市场源",
|
|
435
|
+
"market.installed": "已装 {count}",
|
|
436
|
+
"market.updatable": "可更新 {count}",
|
|
437
|
+
"market.deletedUpstream": "上游已删 {count}",
|
|
438
|
+
"market.checkAll": "检查全部",
|
|
439
|
+
"market.updateAll": "全部更新",
|
|
440
|
+
"market.updateAllConfirmTitle": "批量更新全部技能?",
|
|
441
|
+
"market.updateAllConfirmText": "将把以下来源的可更新技能更新到上游最新版本(覆盖本地修改):",
|
|
442
|
+
"market.updateAllItem": "{repo}({count} 个技能)",
|
|
443
|
+
"market.updateAllDone": "已更新 {count} 个来源",
|
|
444
|
+
"market.deleteSource": "移除源",
|
|
445
|
+
"market.scan": "扫描",
|
|
446
|
+
"market.scanning": "扫描中…",
|
|
447
|
+
"market.check": "检查",
|
|
448
|
+
"market.sync": "同步",
|
|
449
|
+
"market.syncing": "同步中…",
|
|
450
|
+
"market.updated": "有更新",
|
|
451
|
+
"market.newRelease": "新版本 v{version}",
|
|
452
|
+
"market.branchTitle": "选择分支",
|
|
453
|
+
"market.branchHint": "该仓库没有正式发布,选择要跟踪的分支(默认为 main):",
|
|
454
|
+
"market.branchConfirm": "确定",
|
|
455
|
+
"market.syncTitle": "同步本地技能",
|
|
456
|
+
"market.syncText": "市场源已对齐到 {ref},要同步更新以下本地技能吗?",
|
|
457
|
+
"market.syncNone": "该来源还没有导入本地技能。",
|
|
458
|
+
"market.syncCancel": "跳过",
|
|
459
|
+
"market.removeHint": "移除源不影响已安装的技能。",
|
|
460
|
+
"repo.ready": "发现 {count} 个可导入技能",
|
|
461
|
+
"repo.empty": "没有找到可导入技能。",
|
|
462
|
+
"repo.selectAll": "全选",
|
|
463
|
+
"repo.clearAll": "清空",
|
|
464
|
+
"repo.import": "导入所选 ({count})",
|
|
465
|
+
"repo.importing": "导入中…",
|
|
466
|
+
"repo.existing": "已存在",
|
|
467
|
+
"repo.files": "{count} 个文件 · {size}",
|
|
468
|
+
"repo.imported": "导入成功 {count} 个",
|
|
469
|
+
"repo.skippedExisting": "跳过已存在 {count} 个",
|
|
470
|
+
"repo.failed": "失败 {count} 个",
|
|
471
|
+
"repo.root.skills": "技能 skills",
|
|
472
|
+
"repo.root.designTemplates": "模板 design-templates",
|
|
473
|
+
"update.check": "检查更新",
|
|
474
|
+
"update.checking": "检查中…",
|
|
475
|
+
"update.upToDate": "已是最新版本 v{version}",
|
|
476
|
+
"update.available": "发现新版本 v{version}(当前 v{current})",
|
|
477
|
+
"update.newVersion": "新版本 v{version}",
|
|
478
|
+
"update.viewRelease": "查看发布",
|
|
479
|
+
"update.error": "检查更新失败:{error}",
|
|
480
|
+
"update.unavailable": "暂无可用的发布版本",
|
|
186
481
|
"group.project-dsh": "项目 .dsh/skills",
|
|
187
482
|
"group.project-agents": "项目 .agents/skills",
|
|
188
483
|
"group.custom": "自定义目录",
|
|
@@ -191,19 +486,80 @@ window.__ModuleLoader__.load({
|
|
|
191
486
|
"group.user-agents": "用户 ~/.agents/skills",
|
|
192
487
|
"group.bundled": "内置",
|
|
193
488
|
"group.other": "其他来源",
|
|
194
|
-
"
|
|
195
|
-
"
|
|
196
|
-
"
|
|
197
|
-
"
|
|
489
|
+
"groups.new": "新建场景",
|
|
490
|
+
"groups.namePlaceholder": "场景名",
|
|
491
|
+
"groups.edit": "编辑",
|
|
492
|
+
"groups.rename": "保存名称",
|
|
493
|
+
"groups.delete": "删除场景",
|
|
494
|
+
"groups.saveMembers": "保存成员",
|
|
495
|
+
"groups.membersHint": "勾选技能加入此场景(含已禁用)。",
|
|
496
|
+
"groups.empty": "还没有场景。先新建一个,再把技能归类。",
|
|
497
|
+
"groups.noCollections": "暂无来源组。从市场导入技能后自动按来源聚合。",
|
|
498
|
+
"groups.personal": "个人",
|
|
499
|
+
"groups.noWritable": "该组没有可写技能(只读技能无法由中枢开关)",
|
|
500
|
+
"groups.closeAll": "全部关闭",
|
|
501
|
+
"groups.keepOn": "保留开启",
|
|
502
|
+
"groups.conflictTitle": "关闭此组?",
|
|
503
|
+
"groups.conflictText": "以下技能也在其他组中开启,关闭它们会影响那些组:",
|
|
504
|
+
"filter.allSources": "全部来源",
|
|
505
|
+
"filter.private": "个人",
|
|
506
|
+
"sort.name": "按名称",
|
|
507
|
+
"sort.added": "按添加时间",
|
|
508
|
+
"sort.uses": "按调用次数",
|
|
509
|
+
"time.justNow": "刚刚",
|
|
510
|
+
"time.minutesAgo": "{value} 分钟前",
|
|
511
|
+
"time.hoursAgo": "{value} 小时前",
|
|
512
|
+
"time.daysAgo": "{value} 天前",
|
|
513
|
+
"time.weeksAgo": "{value} 周前",
|
|
514
|
+
"badge.source.project-dsh": "项目 .dsh",
|
|
515
|
+
"badge.source.project-agents": "项目 .agents",
|
|
516
|
+
"badge.source.custom": "自定义",
|
|
517
|
+
"badge.source.runtime": "运行时",
|
|
518
|
+
"badge.source.user-dsh": "用户 .dsh",
|
|
519
|
+
"badge.source.user-agents": "用户 .agents",
|
|
520
|
+
"badge.source.bundled": "内置",
|
|
198
521
|
"row.readonly": "只读",
|
|
199
522
|
"row.disable": "禁用",
|
|
200
523
|
"row.enable": "启用",
|
|
524
|
+
"row.delete": "移入回收站",
|
|
525
|
+
"delete.confirmTitle": "移入回收站?",
|
|
526
|
+
"delete.confirmText": "确定把「{name}」移入回收站吗?可随时恢复。",
|
|
527
|
+
"delete.confirm": "移入回收站",
|
|
201
528
|
"detail.back": "返回列表",
|
|
202
529
|
"detail.provider": "提供者",
|
|
203
|
-
"detail.
|
|
530
|
+
"detail.addedAt": "添加时间",
|
|
531
|
+
"detail.updatedAt": "更新时间",
|
|
204
532
|
"detail.path": "路径",
|
|
533
|
+
"detail.whenToUse": "适用场景",
|
|
534
|
+
"detail.uses": "调用次数",
|
|
535
|
+
"detail.groups": "所属场景",
|
|
205
536
|
"detail.loading": "读取技能正文…",
|
|
206
|
-
"
|
|
537
|
+
"source.private": "个人技能(未绑定上游,不检查更新)",
|
|
538
|
+
"source.commit": "已同步版本",
|
|
539
|
+
"source.unverified": "尚未核对上游",
|
|
540
|
+
"source.check": "检查更新",
|
|
541
|
+
"source.checkHint": "点击检查上游更新",
|
|
542
|
+
"source.checking": "检查中…",
|
|
543
|
+
"source.upToDate": "已是最新",
|
|
544
|
+
"source.updated": "有更新 {count} 个",
|
|
545
|
+
"source.deleted": "上游已删除 {count} 个",
|
|
546
|
+
"source.error": "检查失败",
|
|
547
|
+
"source.throttled": "刚刚检查过",
|
|
548
|
+
"source.sync": "同步",
|
|
549
|
+
"source.syncing": "同步中…",
|
|
550
|
+
"source.syncConfirmTitle": "同步技能?",
|
|
551
|
+
"source.syncConfirmText": "将用上游最新版本覆盖本地对以下技能的所有修改:",
|
|
552
|
+
"source.followDelete": "跟进删除",
|
|
553
|
+
"source.deleteConfirmTitle": "跟进上游删除?",
|
|
554
|
+
"source.deleteConfirmText": "以下技能已从上游仓库移除,本地目录将移入回收站(可恢复):",
|
|
555
|
+
"source.trash": "回收站",
|
|
556
|
+
"source.restore": "恢复",
|
|
557
|
+
"source.clearTrash": "清空回收站",
|
|
558
|
+
"source.clearTrashConfirmTitle": "清空回收站?",
|
|
559
|
+
"source.clearTrashConfirmText": "回收站里的技能会被永久删除,无法恢复。此操作不可撤销。",
|
|
560
|
+
"source.clearTrashConfirm": "永久删除",
|
|
561
|
+
"form.capabilityHint": "能力怎么选:重复方法 → 技能;外部系统 → MCP;长期资料 → 知识库。",
|
|
562
|
+
"form.descPlaceholder": "一句话说明用途(含触发词),Agent 靠它自动激活技能",
|
|
207
563
|
"form.name": "技能名(kebab-case,如 code-review)",
|
|
208
564
|
"form.desc": "描述(一句话路由说明)",
|
|
209
565
|
"form.root": "目标目录",
|
|
@@ -214,35 +570,52 @@ window.__ModuleLoader__.load({
|
|
|
214
570
|
"form.error": "创建失败:",
|
|
215
571
|
"err.dismiss": "知道了",
|
|
216
572
|
"settings.title": "Skill Hub",
|
|
217
|
-
"settings.description": "管理 dsh
|
|
573
|
+
"settings.description": "管理 dsh 本地技能目录:浏览、分组、来源同步与新建。",
|
|
218
574
|
"settings.enabled": "启用插件",
|
|
219
575
|
"settings.enabledHint": "关闭后技能中枢的路由、入口与公告全部下线。",
|
|
220
576
|
"settings.announceToAgent": "向 Agent 公告",
|
|
221
577
|
"settings.announceToAgentHint": "在系统提示中加入本插件说明,用户提到技能管理时 Agent 知道如何协作。",
|
|
578
|
+
"settings.dotModelColor": "模型可调圆点颜色",
|
|
579
|
+
"settings.dotModelColorHint": "技能名旁蓝色圆点的颜色(#rrggbb)。",
|
|
580
|
+
"settings.dotUserColor": "用户可调圆点颜色",
|
|
581
|
+
"settings.dotUserColorHint": "技能名旁绿色圆点的颜色(#rrggbb)。",
|
|
582
|
+
"settings.showUseCount": "显示调用次数",
|
|
583
|
+
"settings.showUseCountHint": "在技能名旁显示琥珀色调用次数。",
|
|
584
|
+
"settings.showUseTime": "显示最近调用时间",
|
|
585
|
+
"settings.showUseTimeHint": "在技能名行右侧显示相对时间(如「3 天前」)。",
|
|
586
|
+
"settings.showGroupSummary": "显示分组汇总",
|
|
587
|
+
"settings.showGroupSummaryHint": "在分组标题后汇总调用次数与最近调用时间。",
|
|
222
588
|
"settings.inherit": "继承",
|
|
223
589
|
"settings.on": "开",
|
|
224
590
|
"settings.off": "关",
|
|
225
591
|
"settings.overridden": "已覆盖",
|
|
226
|
-
"settings.reset": "
|
|
592
|
+
"settings.reset": "恢复默认",
|
|
227
593
|
"settings.unsaved": "未保存",
|
|
228
594
|
"settings.save": "保存",
|
|
229
595
|
"settings.saving": "保存中…",
|
|
230
|
-
"settings.discard": "
|
|
231
|
-
"settings.saveFailed": "
|
|
232
|
-
"settings.readOnly": "
|
|
233
|
-
"settings.notExposed": "
|
|
234
|
-
"settings.collapse": "
|
|
235
|
-
"settings.expand": "
|
|
596
|
+
"settings.discard": "放弃修改",
|
|
597
|
+
"settings.saveFailed": "本部署没有接受这些值,已保留供你修改。",
|
|
598
|
+
"settings.readOnly": "本部署的设置为只读。",
|
|
599
|
+
"settings.notExposed": "本部署没有挂载该设置命名空间。",
|
|
600
|
+
"settings.collapse": "收起设置",
|
|
601
|
+
"settings.expand": "展开设置"
|
|
236
602
|
};
|
|
237
603
|
const en = {
|
|
238
604
|
"entry.label": "Skills",
|
|
239
|
-
"entry.tooltip": "Skill hub: browse and
|
|
240
|
-
"view.
|
|
241
|
-
"view.
|
|
605
|
+
"entry.tooltip": "Skill hub: browse, group, and sync local skills",
|
|
606
|
+
"view.sources": "Sources",
|
|
607
|
+
"view.scenes": "Scenes",
|
|
608
|
+
"view.market": "Market",
|
|
609
|
+
"view.flat": "Flat",
|
|
610
|
+
"view.grouped": "Grouped",
|
|
242
611
|
"panel.title": "Skill Hub",
|
|
243
|
-
"panel.
|
|
612
|
+
"panel.count": "{count} skills total",
|
|
613
|
+
"panel.disabledCount": "{count} disabled",
|
|
244
614
|
"panel.new": "New Skill",
|
|
245
615
|
"panel.search": "Search skills…",
|
|
616
|
+
"legend.model": "Model-invocable — agents may call automatically",
|
|
617
|
+
"legend.user": "User-invocable — explicit invocation only",
|
|
618
|
+
"legend.hint": "Dots sit beside the skill name; no dot means the skill is not invocable.",
|
|
246
619
|
"panel.enabled": "Enabled",
|
|
247
620
|
"panel.disabled": "Disabled",
|
|
248
621
|
"panel.diagnostics": "Discovery diagnostics",
|
|
@@ -250,6 +623,65 @@ window.__ModuleLoader__.load({
|
|
|
250
623
|
"panel.emptyAll": "No skills installed yet. Create the first one.",
|
|
251
624
|
"panel.loading": "Loading…",
|
|
252
625
|
"panel.incomplete": "(catalog may be incomplete)",
|
|
626
|
+
"market.addHint": "Codex-style market sources: add a repo (owner/repo or GitHub URL), scan its skills and import; imported skills are tracked upstream automatically.",
|
|
627
|
+
"market.addPlaceholder": "owner/repo or https://github.com/…",
|
|
628
|
+
"market.addSource": "Add source",
|
|
629
|
+
"market.noSources": "No market sources yet. Add one from the built-in catalog below, or enter a GitHub repo.",
|
|
630
|
+
"market.catalogTitle": "Market catalog",
|
|
631
|
+
"market.catalogHint": "Curated skill repos — add one to scan and install, with ongoing update checks.",
|
|
632
|
+
"market.catalog.anthropics": "Official Anthropic skill collection (Claude Skills)",
|
|
633
|
+
"market.catalog.superpowers": "Superpowers community skill collection",
|
|
634
|
+
"market.catalog.mattpocock": "Matt Pocock skill collection",
|
|
635
|
+
"market.catalog.openDesign": "Design skill collection (design-templates)",
|
|
636
|
+
"market.added": "Added",
|
|
637
|
+
"market.add": "Add",
|
|
638
|
+
"market.mySources": "My sources",
|
|
639
|
+
"market.installed": "{count} installed",
|
|
640
|
+
"market.updatable": "{count} updatable",
|
|
641
|
+
"market.deletedUpstream": "{count} deleted upstream",
|
|
642
|
+
"market.checkAll": "Check all",
|
|
643
|
+
"market.updateAll": "Update all",
|
|
644
|
+
"market.updateAllConfirmTitle": "Update every skill?",
|
|
645
|
+
"market.updateAllConfirmText": "The updatable skills of these sources will be overwritten with their latest upstream versions:",
|
|
646
|
+
"market.updateAllItem": "{repo} ({count} skills)",
|
|
647
|
+
"market.updateAllDone": "Updated {count} sources",
|
|
648
|
+
"market.deleteSource": "Remove source",
|
|
649
|
+
"market.scan": "Scan",
|
|
650
|
+
"market.scanning": "Scanning…",
|
|
651
|
+
"market.check": "Check",
|
|
652
|
+
"market.sync": "Sync",
|
|
653
|
+
"market.syncing": "Syncing…",
|
|
654
|
+
"market.updated": "Updates available",
|
|
655
|
+
"market.newRelease": "New release v{version}",
|
|
656
|
+
"market.branchTitle": "Choose a branch",
|
|
657
|
+
"market.branchHint": "This repo has no releases. Pick a branch to track (main by default):",
|
|
658
|
+
"market.branchConfirm": "Confirm",
|
|
659
|
+
"market.syncTitle": "Update local skills",
|
|
660
|
+
"market.syncText": "Market source aligned to {ref}. Update these local skills?",
|
|
661
|
+
"market.syncNone": "No local skills tracked from this repo yet.",
|
|
662
|
+
"market.syncCancel": "Skip",
|
|
663
|
+
"market.removeHint": "Removing a source does not affect installed skills.",
|
|
664
|
+
"repo.ready": "{count} importable skills found",
|
|
665
|
+
"repo.empty": "No importable skills found.",
|
|
666
|
+
"repo.selectAll": "Select all",
|
|
667
|
+
"repo.clearAll": "Clear",
|
|
668
|
+
"repo.import": "Import selected ({count})",
|
|
669
|
+
"repo.importing": "Importing…",
|
|
670
|
+
"repo.existing": "Existing",
|
|
671
|
+
"repo.files": "{count} files · {size}",
|
|
672
|
+
"repo.imported": "Imported {count}",
|
|
673
|
+
"repo.skippedExisting": "Skipped existing {count}",
|
|
674
|
+
"repo.failed": "Failed {count}",
|
|
675
|
+
"repo.root.skills": "Skills",
|
|
676
|
+
"repo.root.designTemplates": "Templates design-templates",
|
|
677
|
+
"update.check": "Check for updates",
|
|
678
|
+
"update.checking": "Checking…",
|
|
679
|
+
"update.upToDate": "Up to date: v{version}",
|
|
680
|
+
"update.available": "Update available: v{version} (current v{current})",
|
|
681
|
+
"update.newVersion": "New version v{version}",
|
|
682
|
+
"update.viewRelease": "View release",
|
|
683
|
+
"update.error": "Update check failed: {error}",
|
|
684
|
+
"update.unavailable": "No release available",
|
|
253
685
|
"group.project-dsh": "Project .dsh/skills",
|
|
254
686
|
"group.project-agents": "Project .agents/skills",
|
|
255
687
|
"group.custom": "Custom directories",
|
|
@@ -258,19 +690,80 @@ window.__ModuleLoader__.load({
|
|
|
258
690
|
"group.user-agents": "User ~/.agents/skills",
|
|
259
691
|
"group.bundled": "Bundled",
|
|
260
692
|
"group.other": "Other sources",
|
|
261
|
-
"
|
|
262
|
-
"
|
|
263
|
-
"
|
|
264
|
-
"
|
|
693
|
+
"groups.new": "New scene",
|
|
694
|
+
"groups.namePlaceholder": "Scene name",
|
|
695
|
+
"groups.edit": "Edit",
|
|
696
|
+
"groups.rename": "Save name",
|
|
697
|
+
"groups.delete": "Delete scene",
|
|
698
|
+
"groups.saveMembers": "Save members",
|
|
699
|
+
"groups.membersHint": "Check skills to add to this scene (disabled skills included).",
|
|
700
|
+
"groups.empty": "No scenes yet. Create one and start categorizing skills.",
|
|
701
|
+
"groups.noCollections": "No source groups yet. Import skills from the market to group them by source.",
|
|
702
|
+
"groups.personal": "Personal",
|
|
703
|
+
"groups.noWritable": "No writable skills in this group (read-only skills cannot be switched from the hub)",
|
|
704
|
+
"groups.closeAll": "Close all",
|
|
705
|
+
"groups.keepOn": "Keep on",
|
|
706
|
+
"groups.conflictTitle": "Close this group?",
|
|
707
|
+
"groups.conflictText": "These skills are also enabled in other groups; closing them affects those groups:",
|
|
708
|
+
"filter.allSources": "All sources",
|
|
709
|
+
"filter.private": "Personal",
|
|
710
|
+
"sort.name": "By name",
|
|
711
|
+
"sort.added": "By added",
|
|
712
|
+
"sort.uses": "By uses",
|
|
713
|
+
"time.justNow": "just now",
|
|
714
|
+
"time.minutesAgo": "{value} min ago",
|
|
715
|
+
"time.hoursAgo": "{value} h ago",
|
|
716
|
+
"time.daysAgo": "{value} d ago",
|
|
717
|
+
"time.weeksAgo": "{value} w ago",
|
|
718
|
+
"badge.source.project-dsh": "project .dsh",
|
|
719
|
+
"badge.source.project-agents": "project .agents",
|
|
720
|
+
"badge.source.custom": "custom",
|
|
721
|
+
"badge.source.runtime": "runtime",
|
|
722
|
+
"badge.source.user-dsh": "user .dsh",
|
|
723
|
+
"badge.source.user-agents": "user .agents",
|
|
724
|
+
"badge.source.bundled": "bundled",
|
|
265
725
|
"row.readonly": "read-only",
|
|
266
726
|
"row.disable": "Disable",
|
|
267
727
|
"row.enable": "Enable",
|
|
728
|
+
"row.delete": "Move to trash",
|
|
729
|
+
"delete.confirmTitle": "Move to trash?",
|
|
730
|
+
"delete.confirmText": "Move \"{name}\" to trash? It can be restored anytime.",
|
|
731
|
+
"delete.confirm": "Move to trash",
|
|
268
732
|
"detail.back": "Back to list",
|
|
269
733
|
"detail.provider": "Provider",
|
|
270
|
-
"detail.
|
|
734
|
+
"detail.addedAt": "Added",
|
|
735
|
+
"detail.updatedAt": "Updated",
|
|
271
736
|
"detail.path": "Path",
|
|
737
|
+
"detail.whenToUse": "When to use",
|
|
738
|
+
"detail.uses": "Uses",
|
|
739
|
+
"detail.groups": "Groups",
|
|
272
740
|
"detail.loading": "Loading skill body…",
|
|
273
|
-
"
|
|
741
|
+
"source.private": "Personal skill (not bound to an upstream; no update checks)",
|
|
742
|
+
"source.commit": "Synced commit",
|
|
743
|
+
"source.unverified": "upstream not verified yet",
|
|
744
|
+
"source.check": "Check for updates",
|
|
745
|
+
"source.checkHint": "Click to check the upstream for updates",
|
|
746
|
+
"source.checking": "Checking…",
|
|
747
|
+
"source.upToDate": "Up to date",
|
|
748
|
+
"source.updated": "{count} updates",
|
|
749
|
+
"source.deleted": "{count} deleted upstream",
|
|
750
|
+
"source.error": "Check failed",
|
|
751
|
+
"source.throttled": "Checked recently",
|
|
752
|
+
"source.sync": "Sync",
|
|
753
|
+
"source.syncing": "Syncing…",
|
|
754
|
+
"source.syncConfirmTitle": "Sync skills?",
|
|
755
|
+
"source.syncConfirmText": "This overwrites local changes to these skills with the latest upstream version:",
|
|
756
|
+
"source.followDelete": "Follow deletion",
|
|
757
|
+
"source.deleteConfirmTitle": "Follow upstream deletion?",
|
|
758
|
+
"source.deleteConfirmText": "These skills were removed upstream; their local directories move to trash (restorable):",
|
|
759
|
+
"source.trash": "Trash",
|
|
760
|
+
"source.restore": "Restore",
|
|
761
|
+
"source.clearTrash": "Empty trash",
|
|
762
|
+
"source.clearTrashConfirmTitle": "Empty the trash?",
|
|
763
|
+
"source.clearTrashConfirmText": "Skills in the trash will be permanently deleted and cannot be restored.",
|
|
764
|
+
"source.clearTrashConfirm": "Delete permanently",
|
|
765
|
+
"form.capabilityHint": "Choosing a capability: repeatable method → skill; external system → MCP; long-term knowledge → knowledge base.",
|
|
766
|
+
"form.descPlaceholder": "One line on what it does (with trigger words) — agents auto-activate from this",
|
|
274
767
|
"form.name": "Skill name (kebab-case, e.g. code-review)",
|
|
275
768
|
"form.desc": "Description (one routing sentence)",
|
|
276
769
|
"form.root": "Target root",
|
|
@@ -281,25 +774,35 @@ window.__ModuleLoader__.load({
|
|
|
281
774
|
"form.error": "Create failed: ",
|
|
282
775
|
"err.dismiss": "Dismiss",
|
|
283
776
|
"settings.title": "Skill Hub",
|
|
284
|
-
"settings.description": "Manage the local dsh skill catalog: browse,
|
|
777
|
+
"settings.description": "Manage the local dsh skill catalog: browse, group, sync, and scaffold skills.",
|
|
285
778
|
"settings.enabled": "Enable plugin",
|
|
286
779
|
"settings.enabledHint": "Disables the hub routes, entries, and announcement.",
|
|
287
780
|
"settings.announceToAgent": "Announce to agent",
|
|
288
781
|
"settings.announceToAgentHint": "Adds a system-prompt section so agents know how to collaborate when users mention skill management.",
|
|
289
|
-
"settings.
|
|
290
|
-
"settings.
|
|
291
|
-
"settings.
|
|
292
|
-
"settings.
|
|
293
|
-
"settings.
|
|
294
|
-
"settings.
|
|
782
|
+
"settings.dotModelColor": "Model-invocable dot color",
|
|
783
|
+
"settings.dotModelColorHint": "Color of the blue dot beside skill names (#rrggbb).",
|
|
784
|
+
"settings.dotUserColor": "User-invocable dot color",
|
|
785
|
+
"settings.dotUserColorHint": "Color of the green dot beside skill names (#rrggbb).",
|
|
786
|
+
"settings.showUseCount": "Show invocation count",
|
|
787
|
+
"settings.showUseCountHint": "Show the amber invocation count beside each skill name.",
|
|
788
|
+
"settings.showUseTime": "Show last-used time",
|
|
789
|
+
"settings.showUseTimeHint": "Show relative last-used time on the name row (e.g. \"3 d ago\").",
|
|
790
|
+
"settings.showGroupSummary": "Show group summaries",
|
|
791
|
+
"settings.showGroupSummaryHint": "Summarize invocation count and last-used time after group titles.",
|
|
792
|
+
"settings.inherit": "Inherit",
|
|
793
|
+
"settings.on": "On",
|
|
794
|
+
"settings.off": "Off",
|
|
795
|
+
"settings.overridden": "Overridden",
|
|
796
|
+
"settings.reset": "Reset to default",
|
|
797
|
+
"settings.unsaved": "Unsaved",
|
|
295
798
|
"settings.save": "Save",
|
|
296
799
|
"settings.saving": "Saving…",
|
|
297
800
|
"settings.discard": "Discard",
|
|
298
|
-
"settings.saveFailed": "
|
|
299
|
-
"settings.readOnly": "This deployment
|
|
300
|
-
"settings.notExposed": "
|
|
301
|
-
"settings.collapse": "
|
|
302
|
-
"settings.expand": "
|
|
801
|
+
"settings.saveFailed": "The deployment did not accept these values; they were left for you to correct.",
|
|
802
|
+
"settings.readOnly": "This deployment stores settings read-only.",
|
|
803
|
+
"settings.notExposed": "This deployment does not expose the settings namespace.",
|
|
804
|
+
"settings.collapse": "Hide settings",
|
|
805
|
+
"settings.expand": "Show settings"
|
|
303
806
|
};
|
|
304
807
|
/** Interpolate a {name} template with the given values. */
|
|
305
808
|
function t(dict, key, values) {
|
|
@@ -308,8 +811,51 @@ window.__ModuleLoader__.load({
|
|
|
308
811
|
return text;
|
|
309
812
|
}
|
|
310
813
|
//#endregion
|
|
814
|
+
//#region src/client/settings-nav-icon.ts
|
|
815
|
+
/**
|
|
816
|
+
* The settings shell renders section nav icons from a hard-coded id map and
|
|
817
|
+
* exposes no registrant icon option, so the skill-hub section falls back to
|
|
818
|
+
* the generic gear. This scoped DOM patch swaps in the official skill icon
|
|
819
|
+
* whenever the settings dialog re-renders.
|
|
820
|
+
*/
|
|
821
|
+
const SKILL_SECTION_LABELS = ["技能", "Skills"];
|
|
822
|
+
const SKILL_ICON_SVG = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" data-skill-hub-nav-icon="true"><path d="M12.5113 15.4067C12.4395 15.6249 12.1308 15.6249 12.059 15.4067L11.643 14.1416C11.454 13.567 11.0033 13.1164 10.4288 12.9274L9.16369 12.5113C8.94544 12.4395 8.94544 12.1308 9.16369 12.059L10.4288 11.643C11.0033 11.454 11.454 11.0033 11.643 10.4288L12.059 9.16369C12.1308 8.94544 12.4395 8.94544 12.5113 9.16369L12.9274 10.4288C13.1164 11.0033 13.567 11.454 14.1416 11.643L15.4067 12.059C15.6249 12.1308 15.6249 12.4395 15.4067 12.5113L14.1416 12.9274C13.567 13.1164 13.1164 13.567 12.9274 14.1416L12.5113 15.4067Z" fill="currentColor"/><path d="M9.02246 0.546878C9.9822 0.546878 10.7564 0.545403 11.374 0.612307C12.0042 0.680586 12.5515 0.826244 13.0273 1.17188C13.3052 1.37376 13.5501 1.61868 13.752 1.89649C14.0975 2.37225 14.2432 2.91984 14.3115 3.54981C14.3784 4.16727 14.377 4.94206 14.377 5.90137V8.51367C13.9611 8.29533 13.5071 8.13985 13.0273 8.06055V5.90137C13.0273 4.9121 13.0259 4.22322 12.9688 3.69532C12.9129 3.18044 12.8098 2.89782 12.6592 2.69043C12.5406 2.52724 12.3966 2.38326 12.2334 2.26465C12.026 2.11404 11.7437 2.0109 11.2285 1.95508C10.7005 1.89789 10.0122 1.89649 9.02246 1.89649H6.55371C5.56395 1.89649 4.87569 1.89787 4.34766 1.95508C3.83242 2.01092 3.55022 2.11398 3.34278 2.26465C3.17953 2.38329 3.03564 2.52719 2.91699 2.69043C2.76642 2.89782 2.66325 3.18042 2.60742 3.69532C2.55027 4.22322 2.54883 4.9121 2.54883 5.90137V10.0986C2.54883 11.0878 2.55031 11.7768 2.60742 12.3047C2.66326 12.8196 2.76642 13.1032 2.91699 13.3105C3.03558 13.4736 3.17966 13.6178 3.34278 13.7363C3.5502 13.8869 3.83265 13.9901 4.34766 14.0459C4.87568 14.1031 5.56398 14.1035 6.55371 14.1035H8.08399C8.27443 14.6025 8.55077 15.0585 8.89551 15.4541H6.55371C5.59402 15.4541 4.81976 15.4546 4.20215 15.3877C3.57204 15.3194 3.02468 15.1738 2.54883 14.8281C2.27111 14.6263 2.02606 14.3813 1.82422 14.1035C1.47883 13.6278 1.33293 13.08 1.26465 12.4502C1.19783 11.8327 1.19922 11.0579 1.19922 10.0986V5.90137C1.19922 4.94206 1.1978 4.16727 1.26465 3.54981C1.33295 2.91984 1.47867 2.37225 1.82422 1.89649C2.02613 1.61864 2.27098 1.37379 2.54883 1.17188C3.02472 0.826181 3.57197 0.6806 4.20215 0.612307C4.81976 0.545393 5.594 0.546877 6.55371 0.546878H9.02246ZM9.19629 9.14649H4.5459V7.84571H9.19629V9.14649ZM11.0303 6.10645H4.5459V4.80567H11.0303V6.10645Z" fill="currentColor"/></svg>`;
|
|
823
|
+
let iconTemplate = null;
|
|
824
|
+
function skillIcon() {
|
|
825
|
+
if (iconTemplate === null) iconTemplate = new DOMParser().parseFromString(SKILL_ICON_SVG, "image/svg+xml").documentElement;
|
|
826
|
+
return iconTemplate.cloneNode(true);
|
|
827
|
+
}
|
|
828
|
+
function patchNavIcon() {
|
|
829
|
+
const dialog = document.querySelector("[role=\"dialog\"]");
|
|
830
|
+
if (dialog === null) return;
|
|
831
|
+
for (const button of dialog.querySelectorAll("button")) {
|
|
832
|
+
const label = button.querySelector("span");
|
|
833
|
+
if (label === null || !SKILL_SECTION_LABELS.includes(label.textContent?.trim() ?? "")) continue;
|
|
834
|
+
if (button.querySelector("svg[data-skill-hub-nav-icon]") !== null) continue;
|
|
835
|
+
const current = button.querySelector("svg");
|
|
836
|
+
const icon = skillIcon();
|
|
837
|
+
if (current !== null) current.replaceWith(icon);
|
|
838
|
+
else button.insertBefore(icon, button.firstChild);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
/** Swap the settings nav gear for the skill icon and keep it in sync. */
|
|
842
|
+
function applySettingsNavIcon() {
|
|
843
|
+
if (typeof document === "undefined" || typeof MutationObserver === "undefined") return () => {};
|
|
844
|
+
patchNavIcon();
|
|
845
|
+
const observer = new MutationObserver(() => {
|
|
846
|
+
patchNavIcon();
|
|
847
|
+
});
|
|
848
|
+
observer.observe(document.body, {
|
|
849
|
+
childList: true,
|
|
850
|
+
subtree: true
|
|
851
|
+
});
|
|
852
|
+
return () => {
|
|
853
|
+
observer.disconnect();
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
//#endregion
|
|
311
857
|
//#region \0dsh-css:/Users/huanyi/Documents/personal/tools/dsh-skill-hub/src/client/settings-card.module.css.mjs
|
|
312
|
-
const css$1 = ".TqD_hG_card{border:1px solid var(--dsw-alias-border-l2
|
|
858
|
+
const css$1 = ".TqD_hG_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.TqD_hG_card:hover{border-color:var(--dsw-alias-label-dimmed)}.TqD_hG_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.TqD_hG_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.TqD_hG_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.TqD_hG_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.TqD_hG_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.TqD_hG_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.TqD_hG_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.TqD_hG_chevron,.TqD_hG_chevronOpen{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.TqD_hG_chevronOpen{transform:rotate(180deg)}.TqD_hG_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.TqD_hG_readOnly,.TqD_hG_notExposed{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.TqD_hG_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.TqD_hG_failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}.TqD_hG_discard,.TqD_hG_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.TqD_hG_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.TqD_hG_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.TqD_hG_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.TqD_hG_discard:disabled,.TqD_hG_save:disabled{opacity:.4;cursor:default}.TqD_hG_discard:focus-visible,.TqD_hG_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.TqD_hG_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.TqD_hG_field+.TqD_hG_field{border-top:1px solid var(--dsw-alias-border-l2)}.TqD_hG_head{align-items:center;gap:8px;display:flex}.TqD_hG_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.TqD_hG_badges{align-items:center;gap:8px;display:inline-flex}.TqD_hG_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.TqD_hG_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.TqD_hG_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.TqD_hG_reset:disabled{cursor:default}.TqD_hG_input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.TqD_hG_input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.TqD_hG_input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.TqD_hG_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}.TqD_hG_colorRow{align-items:center;gap:8px;display:flex}.TqD_hG_colorInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);cursor:pointer;border-radius:8px;width:34px;height:34px;padding:0}.TqD_hG_colorInput:disabled{opacity:.5;cursor:default}.TqD_hG_colorInput::-webkit-color-swatch-wrapper{padding:2px}.TqD_hG_colorInput::-webkit-color-swatch{border:none;border-radius:4px}.TqD_hG_colorText{flex:1;min-width:0}.TqD_hG_switchRow{align-items:center;gap:12px;display:flex}.TqD_hG_switchText{flex:1;min-width:0}.TqD_hG_switch,.TqD_hG_switchOn{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);cursor:pointer;border-radius:999px;flex:none;width:38px;height:22px;padding:2px;transition:background .15s,border-color .15s}.TqD_hG_switchOn{border-color:var(--dsw-alias-state-business-primary,#2f81f7);background:var(--dsw-alias-state-business-primary,#2f81f7)}.TqD_hG_switch:disabled,.TqD_hG_switchOn:disabled{opacity:.4;cursor:default}.TqD_hG_switchThumb{background:#fff;border-radius:50%;width:16px;height:16px;transition:transform .15s;display:block;transform:translate(0);box-shadow:0 1px 2px #00000040}.TqD_hG_switchOn .TqD_hG_switchThumb{transform:translate(16px)}";
|
|
313
859
|
const tagId$1 = "dsh-skill-hub/settings-card.module.css";
|
|
314
860
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
315
861
|
const tag = document.createElement("style");
|
|
@@ -323,8 +869,12 @@ window.__ModuleLoader__.load({
|
|
|
323
869
|
"badges": "TqD_hG_badges",
|
|
324
870
|
"body": "TqD_hG_body",
|
|
325
871
|
"card": "TqD_hG_card",
|
|
872
|
+
"cardOpen": "TqD_hG_cardOpen",
|
|
326
873
|
"chevron": "TqD_hG_chevron",
|
|
327
874
|
"chevronOpen": "TqD_hG_chevronOpen",
|
|
875
|
+
"colorInput": "TqD_hG_colorInput",
|
|
876
|
+
"colorRow": "TqD_hG_colorRow",
|
|
877
|
+
"colorText": "TqD_hG_colorText",
|
|
328
878
|
"description": "TqD_hG_description",
|
|
329
879
|
"discard": "TqD_hG_discard",
|
|
330
880
|
"failed": "TqD_hG_failed",
|
|
@@ -334,6 +884,7 @@ window.__ModuleLoader__.load({
|
|
|
334
884
|
"headText": "TqD_hG_headText",
|
|
335
885
|
"header": "TqD_hG_header",
|
|
336
886
|
"hint": "TqD_hG_hint",
|
|
887
|
+
"input": "TqD_hG_input",
|
|
337
888
|
"label": "TqD_hG_label",
|
|
338
889
|
"name": "TqD_hG_name",
|
|
339
890
|
"notExposed": "TqD_hG_notExposed",
|
|
@@ -341,7 +892,6 @@ window.__ModuleLoader__.load({
|
|
|
341
892
|
"readOnly": "TqD_hG_readOnly",
|
|
342
893
|
"reset": "TqD_hG_reset",
|
|
343
894
|
"save": "TqD_hG_save",
|
|
344
|
-
"select": "TqD_hG_select",
|
|
345
895
|
"switch": "TqD_hG_switch",
|
|
346
896
|
"switchOn": "TqD_hG_switchOn",
|
|
347
897
|
"switchRow": "TqD_hG_switchRow",
|
|
@@ -351,10 +901,9 @@ window.__ModuleLoader__.load({
|
|
|
351
901
|
//#endregion
|
|
352
902
|
//#region src/client/settings-card.tsx
|
|
353
903
|
/**
|
|
354
|
-
* Shared chrome for the plugin settings card,
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
* controls inside, and the save that writes them. Renders nothing while the
|
|
904
|
+
* Shared chrome for the plugin settings card, aligned with the official
|
|
905
|
+
* dsh-client-ui-settings-plugins PluginCard design language: the same tokens,
|
|
906
|
+
* disclosure header, field rhythm, and copy. Renders nothing while the
|
|
358
907
|
* namespace is unavailable — a deployment that does not compose the owning
|
|
359
908
|
* plugin should show no trace of it.
|
|
360
909
|
*/
|
|
@@ -392,14 +941,11 @@ window.__ModuleLoader__.load({
|
|
|
392
941
|
className: settings_card_module_css_default.pending,
|
|
393
942
|
children: props.t("settings.unsaved")
|
|
394
943
|
}) : null,
|
|
395
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
396
|
-
className: open ? settings_card_module_css_default.chevronOpen : settings_card_module_css_default.chevron,
|
|
397
|
-
children: "▾"
|
|
398
|
-
})
|
|
944
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: open ? settings_card_module_css_default.chevronOpen : settings_card_module_css_default.chevron })
|
|
399
945
|
]
|
|
400
946
|
});
|
|
401
947
|
if (!state.exposed) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
402
|
-
className: settings_card_module_css_default.card,
|
|
948
|
+
className: settings_card_module_css_default.card + (open ? " " + settings_card_module_css_default.cardOpen : ""),
|
|
403
949
|
children: [header, open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
404
950
|
className: settings_card_module_css_default.body,
|
|
405
951
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -410,7 +956,7 @@ window.__ModuleLoader__.load({
|
|
|
410
956
|
}) : null]
|
|
411
957
|
});
|
|
412
958
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
413
|
-
className: settings_card_module_css_default.card,
|
|
959
|
+
className: settings_card_module_css_default.card + (open ? " " + settings_card_module_css_default.cardOpen : ""),
|
|
414
960
|
children: [header, open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
415
961
|
className: settings_card_module_css_default.body,
|
|
416
962
|
children: [
|
|
@@ -448,60 +994,6 @@ window.__ModuleLoader__.load({
|
|
|
448
994
|
}) : null]
|
|
449
995
|
});
|
|
450
996
|
}
|
|
451
|
-
function BooleanField(props) {
|
|
452
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
453
|
-
className: settings_card_module_css_default.field,
|
|
454
|
-
children: [
|
|
455
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
456
|
-
className: settings_card_module_css_default.head,
|
|
457
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
458
|
-
className: settings_card_module_css_default.label,
|
|
459
|
-
htmlFor: props.id,
|
|
460
|
-
children: props.label
|
|
461
|
-
}), props.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
462
|
-
className: settings_card_module_css_default.badges,
|
|
463
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
464
|
-
className: settings_card_module_css_default.badge,
|
|
465
|
-
children: props.overriddenLabel
|
|
466
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
467
|
-
type: "button",
|
|
468
|
-
className: settings_card_module_css_default.reset,
|
|
469
|
-
disabled: props.disabled,
|
|
470
|
-
onClick: props.onReset,
|
|
471
|
-
children: props.resetLabel
|
|
472
|
-
})]
|
|
473
|
-
}) : null]
|
|
474
|
-
}),
|
|
475
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
476
|
-
id: props.id,
|
|
477
|
-
className: settings_card_module_css_default.select,
|
|
478
|
-
value: props.text,
|
|
479
|
-
disabled: props.disabled,
|
|
480
|
-
onChange: (event) => {
|
|
481
|
-
props.onEdit(event.target.value);
|
|
482
|
-
},
|
|
483
|
-
children: [
|
|
484
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
485
|
-
value: "",
|
|
486
|
-
children: props.inheritLabel
|
|
487
|
-
}),
|
|
488
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
489
|
-
value: "true",
|
|
490
|
-
children: props.onLabel
|
|
491
|
-
}),
|
|
492
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
493
|
-
value: "false",
|
|
494
|
-
children: props.offLabel
|
|
495
|
-
})
|
|
496
|
-
]
|
|
497
|
-
}),
|
|
498
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
499
|
-
className: settings_card_module_css_default.hint,
|
|
500
|
-
children: props.hint
|
|
501
|
-
})
|
|
502
|
-
]
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
997
|
function SwitchField(props) {
|
|
506
998
|
const checked = props.text !== "false";
|
|
507
999
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -547,6 +1039,60 @@ window.__ModuleLoader__.load({
|
|
|
547
1039
|
})
|
|
548
1040
|
});
|
|
549
1041
|
}
|
|
1042
|
+
function ColorField(props) {
|
|
1043
|
+
const value = /^#[0-9a-f]{6}$/i.test(props.text) ? props.text : props.defaultColor;
|
|
1044
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1045
|
+
className: settings_card_module_css_default.field,
|
|
1046
|
+
children: [
|
|
1047
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1048
|
+
className: settings_card_module_css_default.head,
|
|
1049
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
1050
|
+
className: settings_card_module_css_default.label,
|
|
1051
|
+
htmlFor: props.id,
|
|
1052
|
+
children: props.label
|
|
1053
|
+
}), props.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1054
|
+
className: settings_card_module_css_default.badges,
|
|
1055
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1056
|
+
className: settings_card_module_css_default.badge,
|
|
1057
|
+
children: props.overriddenLabel
|
|
1058
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1059
|
+
type: "button",
|
|
1060
|
+
className: settings_card_module_css_default.reset,
|
|
1061
|
+
disabled: props.disabled,
|
|
1062
|
+
onClick: props.onReset,
|
|
1063
|
+
children: props.resetLabel
|
|
1064
|
+
})]
|
|
1065
|
+
}) : null]
|
|
1066
|
+
}),
|
|
1067
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1068
|
+
className: settings_card_module_css_default.colorRow,
|
|
1069
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1070
|
+
id: props.id,
|
|
1071
|
+
type: "color",
|
|
1072
|
+
className: settings_card_module_css_default.colorInput,
|
|
1073
|
+
value,
|
|
1074
|
+
disabled: props.disabled,
|
|
1075
|
+
onChange: (event) => {
|
|
1076
|
+
props.onEdit(event.target.value);
|
|
1077
|
+
}
|
|
1078
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1079
|
+
type: "text",
|
|
1080
|
+
className: settings_card_module_css_default.input + " " + settings_card_module_css_default.colorText,
|
|
1081
|
+
value: props.text,
|
|
1082
|
+
disabled: props.disabled,
|
|
1083
|
+
placeholder: props.inheritLabel,
|
|
1084
|
+
onChange: (event) => {
|
|
1085
|
+
props.onEdit(event.target.value.trim());
|
|
1086
|
+
}
|
|
1087
|
+
})]
|
|
1088
|
+
}),
|
|
1089
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1090
|
+
className: settings_card_module_css_default.hint,
|
|
1091
|
+
children: props.hint
|
|
1092
|
+
})
|
|
1093
|
+
]
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
550
1096
|
//#endregion
|
|
551
1097
|
//#region src/client/settings-form.ts
|
|
552
1098
|
/**
|
|
@@ -576,6 +1122,19 @@ window.__ModuleLoader__.load({
|
|
|
576
1122
|
}
|
|
577
1123
|
};
|
|
578
1124
|
}
|
|
1125
|
+
/** A #rrggbb color field, edited through hex draft text. */
|
|
1126
|
+
function colorField(field) {
|
|
1127
|
+
return {
|
|
1128
|
+
field,
|
|
1129
|
+
format: (value) => typeof value === "string" && /^#[0-9a-f]{6}$/i.test(value) ? value : "",
|
|
1130
|
+
parse: (text) => {
|
|
1131
|
+
if (/^#[0-9a-f]{6}$/i.test(text)) return {
|
|
1132
|
+
kind: "set",
|
|
1133
|
+
value: text
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
579
1138
|
/** Stages one card's edits and writes them through the settings transport. */
|
|
580
1139
|
var CardForm = class {
|
|
581
1140
|
scope;
|
|
@@ -748,14 +1307,27 @@ window.__ModuleLoader__.load({
|
|
|
748
1307
|
store;
|
|
749
1308
|
/** @param scope - the hub config scope the card edits (ApiConfigScope). */
|
|
750
1309
|
constructor(scope) {
|
|
751
|
-
this.form = new CardForm(scope, [
|
|
1310
|
+
this.form = new CardForm(scope, [
|
|
1311
|
+
booleanField("enabled"),
|
|
1312
|
+
booleanField("announceToAgent"),
|
|
1313
|
+
colorField("dotModelColor"),
|
|
1314
|
+
colorField("dotUserColor"),
|
|
1315
|
+
booleanField("showUseCount"),
|
|
1316
|
+
booleanField("showUseTime"),
|
|
1317
|
+
booleanField("showGroupSummary")
|
|
1318
|
+
]);
|
|
752
1319
|
this.store = this.form.bind(() => this.projection());
|
|
753
1320
|
}
|
|
754
1321
|
projection() {
|
|
755
1322
|
return {
|
|
756
1323
|
...this.form.shell(),
|
|
757
1324
|
enabled: this.form.field("enabled"),
|
|
758
|
-
announceToAgent: this.form.field("announceToAgent")
|
|
1325
|
+
announceToAgent: this.form.field("announceToAgent"),
|
|
1326
|
+
dotModelColor: this.form.field("dotModelColor"),
|
|
1327
|
+
dotUserColor: this.form.field("dotUserColor"),
|
|
1328
|
+
showUseCount: this.form.field("showUseCount"),
|
|
1329
|
+
showUseTime: this.form.field("showUseTime"),
|
|
1330
|
+
showGroupSummary: this.form.field("showGroupSummary")
|
|
759
1331
|
};
|
|
760
1332
|
}
|
|
761
1333
|
/**
|
|
@@ -790,33 +1362,98 @@ window.__ModuleLoader__.load({
|
|
|
790
1362
|
state,
|
|
791
1363
|
onSave: props.save,
|
|
792
1364
|
onDiscard: props.discard,
|
|
793
|
-
children: [
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
1365
|
+
children: [
|
|
1366
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SwitchField, {
|
|
1367
|
+
label: t("settings.enabled"),
|
|
1368
|
+
hint: t("settings.enabledHint"),
|
|
1369
|
+
...fieldProps,
|
|
1370
|
+
...state.enabled,
|
|
1371
|
+
onEdit: (text) => {
|
|
1372
|
+
props.edit("enabled", text);
|
|
1373
|
+
},
|
|
1374
|
+
onReset: () => {
|
|
1375
|
+
props.resetField("enabled");
|
|
1376
|
+
}
|
|
1377
|
+
}),
|
|
1378
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SwitchField, {
|
|
1379
|
+
label: t("settings.announceToAgent"),
|
|
1380
|
+
hint: t("settings.announceToAgentHint"),
|
|
1381
|
+
...fieldProps,
|
|
1382
|
+
...state.announceToAgent,
|
|
1383
|
+
onEdit: (text) => {
|
|
1384
|
+
props.edit("announceToAgent", text);
|
|
1385
|
+
},
|
|
1386
|
+
onReset: () => {
|
|
1387
|
+
props.resetField("announceToAgent");
|
|
1388
|
+
}
|
|
1389
|
+
}),
|
|
1390
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ColorField, {
|
|
1391
|
+
id: "skill-hub-dot-model-color",
|
|
1392
|
+
label: t("settings.dotModelColor"),
|
|
1393
|
+
hint: t("settings.dotModelColorHint"),
|
|
1394
|
+
inheritLabel: t("settings.inherit"),
|
|
1395
|
+
defaultColor: DEFAULT_DOT_MODEL_COLOR,
|
|
1396
|
+
...fieldProps,
|
|
1397
|
+
...state.dotModelColor,
|
|
1398
|
+
onEdit: (text) => {
|
|
1399
|
+
props.edit("dotModelColor", text);
|
|
1400
|
+
},
|
|
1401
|
+
onReset: () => {
|
|
1402
|
+
props.resetField("dotModelColor");
|
|
1403
|
+
}
|
|
1404
|
+
}),
|
|
1405
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ColorField, {
|
|
1406
|
+
id: "skill-hub-dot-user-color",
|
|
1407
|
+
label: t("settings.dotUserColor"),
|
|
1408
|
+
hint: t("settings.dotUserColorHint"),
|
|
1409
|
+
inheritLabel: t("settings.inherit"),
|
|
1410
|
+
defaultColor: DEFAULT_DOT_USER_COLOR,
|
|
1411
|
+
...fieldProps,
|
|
1412
|
+
...state.dotUserColor,
|
|
1413
|
+
onEdit: (text) => {
|
|
1414
|
+
props.edit("dotUserColor", text);
|
|
1415
|
+
},
|
|
1416
|
+
onReset: () => {
|
|
1417
|
+
props.resetField("dotUserColor");
|
|
1418
|
+
}
|
|
1419
|
+
}),
|
|
1420
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SwitchField, {
|
|
1421
|
+
label: t("settings.showUseCount"),
|
|
1422
|
+
hint: t("settings.showUseCountHint"),
|
|
1423
|
+
...fieldProps,
|
|
1424
|
+
...state.showUseCount,
|
|
1425
|
+
onEdit: (text) => {
|
|
1426
|
+
props.edit("showUseCount", text);
|
|
1427
|
+
},
|
|
1428
|
+
onReset: () => {
|
|
1429
|
+
props.resetField("showUseCount");
|
|
1430
|
+
}
|
|
1431
|
+
}),
|
|
1432
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SwitchField, {
|
|
1433
|
+
label: t("settings.showUseTime"),
|
|
1434
|
+
hint: t("settings.showUseTimeHint"),
|
|
1435
|
+
...fieldProps,
|
|
1436
|
+
...state.showUseTime,
|
|
1437
|
+
onEdit: (text) => {
|
|
1438
|
+
props.edit("showUseTime", text);
|
|
1439
|
+
},
|
|
1440
|
+
onReset: () => {
|
|
1441
|
+
props.resetField("showUseTime");
|
|
1442
|
+
}
|
|
1443
|
+
}),
|
|
1444
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SwitchField, {
|
|
1445
|
+
label: t("settings.showGroupSummary"),
|
|
1446
|
+
hint: t("settings.showGroupSummaryHint"),
|
|
1447
|
+
...fieldProps,
|
|
1448
|
+
...state.showGroupSummary,
|
|
1449
|
+
onEdit: (text) => {
|
|
1450
|
+
props.edit("showGroupSummary", text);
|
|
1451
|
+
},
|
|
1452
|
+
onReset: () => {
|
|
1453
|
+
props.resetField("showGroupSummary");
|
|
1454
|
+
}
|
|
1455
|
+
})
|
|
1456
|
+
]
|
|
820
1457
|
});
|
|
821
1458
|
}
|
|
822
1459
|
//#endregion
|
|
@@ -839,8 +1476,114 @@ window.__ModuleLoader__.load({
|
|
|
839
1476
|
return String(error);
|
|
840
1477
|
}
|
|
841
1478
|
//#endregion
|
|
1479
|
+
//#region src/client/grouping.ts
|
|
1480
|
+
/**
|
|
1481
|
+
* Derive a group switch view from its member names and the set of currently
|
|
1482
|
+
* enabled skill names (catalog.skills). Members not in the enabled set count
|
|
1483
|
+
* as disabled (catalog.disabled or absent read-only rows).
|
|
1484
|
+
*/
|
|
1485
|
+
function groupSwitchView(members, enabledNames) {
|
|
1486
|
+
const enabled = [];
|
|
1487
|
+
const disabled = [];
|
|
1488
|
+
for (const name of members) if (enabledNames.has(name)) enabled.push(name);
|
|
1489
|
+
else disabled.push(name);
|
|
1490
|
+
return {
|
|
1491
|
+
state: disabled.length === 0 ? "on" : enabled.length === 0 ? "off" : "mixed",
|
|
1492
|
+
enabled,
|
|
1493
|
+
disabled
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
/** Names of every group a skill belongs to (tags + collections). */
|
|
1497
|
+
function groupNamesOf(name, tags, collections) {
|
|
1498
|
+
const names = [];
|
|
1499
|
+
for (const tag of tags) if (tag.skillNames.includes(name)) names.push(tag.name);
|
|
1500
|
+
for (const collection of collections) if (collection.skillNames.includes(name)) names.push(collection.name);
|
|
1501
|
+
return names;
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Members of a group that are currently enabled AND also belong to at least
|
|
1505
|
+
* one other group — the set a "close" action must ask about.
|
|
1506
|
+
*/
|
|
1507
|
+
function conflictsOnClose(members, enabledNames, otherGroups) {
|
|
1508
|
+
return members.filter((name) => {
|
|
1509
|
+
if (!enabledNames.has(name)) return false;
|
|
1510
|
+
return otherGroups.some((group) => group.members.includes(name));
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
/** Origin-repo filter value: skills with no source record (private skills). */
|
|
1514
|
+
const PRIVATE_SOURCE = "private";
|
|
1515
|
+
/**
|
|
1516
|
+
* Apply the origin filter ('all' or a specific origin repo; skills without a
|
|
1517
|
+
* source record count as PRIVATE_SOURCE). The origins map is the store's
|
|
1518
|
+
* skillName → repo derivation, so filtering follows the tracked source
|
|
1519
|
+
* records instead of the filesystem root a skill happens to live under.
|
|
1520
|
+
*/
|
|
1521
|
+
function filterBySource(skills, source, origins) {
|
|
1522
|
+
if (source === "all") return [...skills];
|
|
1523
|
+
return skills.filter((skill) => (origins[skill.name] ?? "private") === source);
|
|
1524
|
+
}
|
|
1525
|
+
/**
|
|
1526
|
+
* Sort a skill list in place-safe copy order: name ascending, added
|
|
1527
|
+
* descending (newest first, unknown addedAt last), or uses descending
|
|
1528
|
+
* (most-called first). Unknown values always trail.
|
|
1529
|
+
*/
|
|
1530
|
+
function sortSkills(skills, key, getUses) {
|
|
1531
|
+
const list = [...skills];
|
|
1532
|
+
if (key === "name") list.sort((a, b) => a.name.localeCompare(b.name));
|
|
1533
|
+
else if (key === "added") list.sort((a, b) => (b.addedAt ?? -Infinity) - (a.addedAt ?? -Infinity));
|
|
1534
|
+
else if (key === "uses") list.sort((a, b) => (getUses?.(b.name) ?? 0) - (getUses?.(a.name) ?? 0));
|
|
1535
|
+
return list;
|
|
1536
|
+
}
|
|
1537
|
+
/** Format an epoch-ms timestamp as a short relative time bucket. */
|
|
1538
|
+
function formatRelativeTime(ms, now = Date.now()) {
|
|
1539
|
+
const diff = Math.max(0, now - ms);
|
|
1540
|
+
const minutes = Math.floor(diff / 6e4);
|
|
1541
|
+
if (minutes < 1) return { key: "time.justNow" };
|
|
1542
|
+
if (minutes < 60) return {
|
|
1543
|
+
key: "time.minutesAgo",
|
|
1544
|
+
value: minutes
|
|
1545
|
+
};
|
|
1546
|
+
const hours = Math.floor(minutes / 60);
|
|
1547
|
+
if (hours < 24) return {
|
|
1548
|
+
key: "time.hoursAgo",
|
|
1549
|
+
value: hours
|
|
1550
|
+
};
|
|
1551
|
+
const days = Math.floor(hours / 24);
|
|
1552
|
+
if (days < 7) return {
|
|
1553
|
+
key: "time.daysAgo",
|
|
1554
|
+
value: days
|
|
1555
|
+
};
|
|
1556
|
+
return {
|
|
1557
|
+
key: "time.weeksAgo",
|
|
1558
|
+
value: Math.floor(days / 7)
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
//#endregion
|
|
1562
|
+
//#region src/client/panel/format.ts
|
|
1563
|
+
/** Dot inline style from the user-chosen color (undefined keeps the CSS default). */
|
|
1564
|
+
function dotStyle(color) {
|
|
1565
|
+
if (color === void 0) return void 0;
|
|
1566
|
+
return {
|
|
1567
|
+
background: color,
|
|
1568
|
+
borderColor: color
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
/** Localized relative-time text for a Unix-ms timestamp. */
|
|
1572
|
+
function relativeTimeText(ms) {
|
|
1573
|
+
const rt = formatRelativeTime(ms);
|
|
1574
|
+
return tt(rt.key, rt.value !== void 0 ? { value: rt.value } : void 0);
|
|
1575
|
+
}
|
|
1576
|
+
/** Localized absolute time for the detail meta (e.g. "2026/8/16 11:00:00"). */
|
|
1577
|
+
function formatDateTime(ms) {
|
|
1578
|
+
return new Date(ms).toLocaleString();
|
|
1579
|
+
}
|
|
1580
|
+
/** Short form of a commit SHA for display. */
|
|
1581
|
+
function shortSha(sha) {
|
|
1582
|
+
return sha.length > 7 ? sha.slice(0, 7) : sha;
|
|
1583
|
+
}
|
|
1584
|
+
//#endregion
|
|
842
1585
|
//#region \0dsh-css:/Users/huanyi/Documents/personal/tools/dsh-skill-hub/src/client/panel/panel.module.css.mjs
|
|
843
|
-
const css = "._6VtqdG_panel{flex-direction:column;gap:10px;max-width:880px;margin:0 auto;padding:16px 18px 28px;display:flex}._6VtqdG_header{flex-wrap:wrap;align-items:center;gap:10px;display:flex}._6VtqdG_title{margin:0;font-size:17px;font-weight:600}._6VtqdG_hint{opacity:.6;font-size:11.5px}._6VtqdG_actions{gap:8px;margin-left:auto;display:flex}._6VtqdG_segmented{border:1px solid #80808059;border-radius:6px;display:flex;overflow:hidden}._6VtqdG_segBtn{color:inherit;cursor:pointer;background:0 0;border:none;padding:4px 10px;font-size:11.5px}._6VtqdG_segBtn+._6VtqdG_segBtn{border-left:1px solid #80808059}._6VtqdG_segBtnActive{background:#8080802e;font-weight:600}._6VtqdG_search{width:100%;color:inherit;background:0 0;border:1px solid #80808059;border-radius:6px;padding:8px 10px;font-size:13px}._6VtqdG_section{flex-direction:column;gap:4px;display:flex}._6VtqdG_sectionTitle{margin:10px 0 2px;font-size:12.5px;font-weight:700}._6VtqdG_groupTitle{opacity:.72;margin:8px 0 2px;font-size:12px;font-weight:600}._6VtqdG_row{cursor:pointer;background:0 0;border:1px solid #8080802e;border-radius:8px;align-items:center;gap:10px;padding:8px 10px;display:flex}._6VtqdG_row:hover{border-color:#80808066}._6VtqdG_rowMain{flex:1;min-width:0}._6VtqdG_rowName{font-size:13.5px;font-weight:600}._6VtqdG_rowDesc{opacity:.65;white-space:nowrap;text-overflow:ellipsis;font-size:12px;overflow:hidden}._6VtqdG_badges{flex:none;align-items:center;gap:4px;display:flex}._6VtqdG_badge{opacity:.85;white-space:nowrap;border:1px solid #8080804d;border-radius:999px;padding:1px 6px;font-size:10.5px}._6VtqdG_badgeModel{color:#2f81f7;border-color:currentColor}._6VtqdG_badgeUser{color:#3fb950;border-color:currentColor}._6VtqdG_badgeUses{color:#d29922;border-color:currentColor}._6VtqdG_badgeReadonly{opacity:.55}._6VtqdG_switchBtn{color:inherit;cursor:pointer;background:0 0;border:1px solid #80808059;border-radius:999px;flex:none;padding:3px 10px;font-size:11.5px}._6VtqdG_switchBtn:disabled{opacity:.5;cursor:default}._6VtqdG_switchOff:hover{color:#d1242f;border-color:#d1242f}._6VtqdG_switchOn{color:#3fb950;border-color:currentColor}._6VtqdG_switchOn:hover{color:#d1242f;border-color:currentColor}._6VtqdG_empty{opacity:.6;text-align:center;padding:14px 0;font-size:12.5px}._6VtqdG_diagRow{background:#d2992214;border-left:3px solid #d29922;border-radius:4px;padding:6px 10px;font-size:12px}._6VtqdG_diagPath{opacity:.8;word-break:break-all;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}._6VtqdG_diagReason{opacity:.7}._6VtqdG_errorBanner{color:#d1242f;background:#d1242f14;border:1px solid #d1242f4d;border-radius:6px;align-items:center;gap:10px;padding:8px 10px;font-size:12.5px;display:flex}._6VtqdG_detail{flex-direction:column;gap:10px;display:flex}._6VtqdG_detailHead{flex-wrap:wrap;align-items:center;gap:10px;display:flex}._6VtqdG_back{color:inherit;cursor:pointer;background:0 0;border:1px solid #80808059;border-radius:6px;padding:4px 10px;font-size:12px}._6VtqdG_detailName{font-size:16px;font-weight:700}._6VtqdG_detailMeta{opacity:.8;flex-direction:column;gap:4px;font-size:12px;display:flex}._6VtqdG_detailMetaLine{word-break:break-all}._6VtqdG_detailContent{white-space:pre-wrap;word-break:break-word;border:1px solid #80808033;border-radius:8px;max-height:60vh;padding:12px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.55;overflow:auto}._6VtqdG_form{border:1px solid #80808040;border-radius:8px;flex-direction:column;gap:8px;padding:12px;display:flex}._6VtqdG_formRow{flex-direction:column;gap:4px;display:flex}._6VtqdG_formLabel{opacity:.75;font-size:11.5px}._6VtqdG_input,._6VtqdG_select{color:inherit;background:0 0;border:1px solid #80808059;border-radius:6px;padding:7px 9px;font-size:13px}._6VtqdG_buttons{align-items:center;gap:8px;display:flex}._6VtqdG_button{cursor:pointer;color:inherit;background:0 0;border:1px solid #80808059;border-radius:6px;padding:6px 12px;font-size:12.5px}._6VtqdG_primary{color:#2f81f7;border-color:#2f81f7}._6VtqdG_primary:disabled{opacity:.5;cursor:default}._6VtqdG_formError{color:#d1242f}._6VtqdG_formSuccess{color:#3fb950}._6VtqdG_muted{opacity:.6}";
|
|
1586
|
+
const css = "._6VtqdG_panel{-webkit-font-smoothing:antialiased;color-scheme:light dark;--hub-model:#2f81f7;--hub-user:#3fb950;flex-direction:column;gap:14px;max-width:720px;margin:0 auto;padding:28px 20px 44px;font-family:-apple-system,BlinkMacSystemFont,SF Pro Text,Helvetica Neue,sans-serif;display:flex}._6VtqdG_header{flex-wrap:wrap;align-items:baseline;gap:10px;display:flex}._6VtqdG_title{letter-spacing:-.02em;margin:0;font-size:24px;font-weight:700}._6VtqdG_hint{opacity:.5;font-size:12px}._6VtqdG_actions{gap:8px;margin-left:auto;display:flex}._6VtqdG_segmented{background:#80808024;border-radius:9px;gap:2px;padding:2px;display:flex}._6VtqdG_segBtn{color:inherit;opacity:.62;cursor:pointer;background:0 0;border:none;border-radius:7px;padding:5px 13px;font-size:12px;transition:background .15s,opacity .15s}._6VtqdG_segBtn:hover{opacity:.9}._6VtqdG_segBtnActive{opacity:1;background:#80808047;font-weight:600}._6VtqdG_search{width:100%;color:inherit;background:#8080801f;border:none;border-radius:10px;outline:none;padding:10px 14px;font-size:13px;transition:background .15s}._6VtqdG_search::placeholder{opacity:.45}._6VtqdG_search:focus{background:#8080802e}._6VtqdG_section{background:#8080800f;border-radius:12px;flex-direction:column;margin-top:4px;display:flex;overflow:hidden;box-shadow:0 1px 2px #00000008,0 4px 12px #0000000a}._6VtqdG_sectionTitle{margin:14px 16px 2px;font-size:13px;font-weight:700}._6VtqdG_groupHead{align-items:center;gap:8px;padding:12px 14px 4px;display:flex}._6VtqdG_groupTitle{opacity:.92;flex:1;min-width:0;font-size:13px;font-weight:600}._6VtqdG_groupOps{flex:none;gap:6px;display:inline-flex}._6VtqdG_opBtn{color:inherit;cursor:pointer;background:#80808021;border:none;border-radius:999px;padding:3px 11px;font-size:11px;transition:background .15s}._6VtqdG_opBtn:hover{background:#80808038}._6VtqdG_opBtn:disabled{opacity:.4;cursor:default}._6VtqdG_row{cursor:pointer;background:0 0;align-items:center;gap:12px;padding:10px 14px;transition:background .12s;display:flex}._6VtqdG_row:hover{background:#80808014}._6VtqdG_rowStatic{cursor:default}._6VtqdG_rowStatic:hover{background:0 0}._6VtqdG_row+._6VtqdG_row{border-top:.5px solid #80808021}._6VtqdG_rowMain{flex:1;min-width:0}._6VtqdG_rowName{align-items:center;gap:5px;font-size:13.5px;display:flex}._6VtqdG_rowNameText{font-weight:600}._6VtqdG_rowDesc{opacity:.52;white-space:nowrap;text-overflow:ellipsis;margin-top:1px;font-size:12.5px;overflow:hidden}._6VtqdG_rowMeta{opacity:.45;white-space:nowrap;flex:none;font-size:11.5px}._6VtqdG_badges{flex:none;align-items:center;gap:6px;display:flex}._6VtqdG_badge{opacity:.8;white-space:nowrap;border:.5px solid #8080804d;border-radius:999px;padding:1px 7px;font-size:10.5px}._6VtqdG_badgeModel{color:var(--hub-model);border-color:currentColor}._6VtqdG_badgeUser{color:var(--hub-user);border-color:currentColor}._6VtqdG_badgeUses{color:#d29922;border-color:currentColor}._6VtqdG_badgeReadonly{opacity:.5}._6VtqdG_switch{cursor:pointer;background:#8080804d;border:none;border-radius:999px;flex:none;width:36px;height:21px;padding:2px;transition:background .18s}._6VtqdG_switch:disabled{opacity:.5;cursor:default}._6VtqdG_switchOn{background:#34c759}._6VtqdG_switchThumb{background:#fff;border-radius:50%;width:17px;height:17px;transition:transform .18s;display:block;transform:translate(0);box-shadow:0 1px 2px #00000040}._6VtqdG_switchOn ._6VtqdG_switchThumb{transform:translate(15px)}._6VtqdG_empty{opacity:.5;text-align:center;padding:20px 0;font-size:13px}._6VtqdG_diagRow{background:#d299220f;border-left:3px solid #d29922;padding:9px 14px;font-size:12px}._6VtqdG_diagPath{opacity:.75;word-break:break-all;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}._6VtqdG_diagReason{opacity:.65;margin-top:1px}._6VtqdG_updateLink{color:inherit;font-weight:600;text-decoration:underline}._6VtqdG_errorBanner{color:#d1242f;white-space:pre-wrap;word-break:break-all;background:#d1242f12;border-radius:10px;align-items:flex-start;gap:10px;padding:10px 14px;font-size:12.5px;display:flex}._6VtqdG_successBanner{color:#3fb950;white-space:pre-wrap;word-break:break-all;background:#3fb95014;border-radius:10px;align-items:flex-start;gap:10px;padding:10px 14px;font-size:12.5px;display:flex}._6VtqdG_detailHead{flex-wrap:wrap;align-items:center;gap:10px;display:flex}._6VtqdG_back{color:inherit;cursor:pointer;background:#8080801f;border:none;border-radius:8px;padding:6px 12px;font-size:12.5px}._6VtqdG_back:hover{background:#80808033}._6VtqdG_detailName{letter-spacing:-.01em;font-size:19px;font-weight:700}._6VtqdG_detailMeta{opacity:.6;flex-direction:column;gap:4px;font-size:12px;display:flex}._6VtqdG_detailMetaLine{word-break:break-all}._6VtqdG_detailContent{white-space:pre-wrap;word-break:break-word;background:#8080800d;border-radius:12px;max-height:62vh;padding:14px 16px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.6;overflow:auto}._6VtqdG_form{background:#8080800d;border-radius:12px;flex-direction:column;gap:10px;padding:14px 16px;display:flex;box-shadow:0 1px 2px #00000008,0 4px 12px #0000000a}._6VtqdG_formRow{flex-direction:column;gap:5px;display:flex}._6VtqdG_formLabel{opacity:.6;font-size:12px}._6VtqdG_input,._6VtqdG_select{color:inherit;background:#8080801f;border:none;border-radius:9px;outline:none;padding:9px 12px;font-size:13px}._6VtqdG_input:focus,._6VtqdG_select:focus{background:#8080802e}._6VtqdG_buttons{align-items:center;gap:8px;display:flex}._6VtqdG_button{cursor:pointer;color:inherit;background:#8080801f;border:none;border-radius:8px;padding:6px 13px;font-size:12.5px;transition:background .15s}._6VtqdG_button:hover{background:#80808033}._6VtqdG_primary{background:var(--hub-model);color:#fff}._6VtqdG_primary:hover{opacity:.92;background:#2f81f7}._6VtqdG_primary:disabled{opacity:.45;cursor:default}._6VtqdG_danger{color:#fff;background:#d1242f}._6VtqdG_danger:hover{opacity:.92;background:#d1242f}._6VtqdG_danger:disabled{opacity:.45;cursor:default}._6VtqdG_formError{color:#d1242f}._6VtqdG_formSuccess{color:#3fb950}._6VtqdG_muted{opacity:.55}._6VtqdG_dot{vertical-align:middle;border-radius:50%;width:7px;height:7px;margin-left:5px;display:inline-block}._6VtqdG_dotModel{background:var(--hub-model)}._6VtqdG_dotUser{background:var(--hub-user)}._6VtqdG_legend{opacity:.55;flex-wrap:wrap;align-items:center;gap:12px;padding:2px 2px 0;font-size:11.5px;display:flex}._6VtqdG_legendItem{align-items:center;gap:4px;display:inline-flex}._6VtqdG_legendItem ._6VtqdG_dot{margin-left:0}._6VtqdG_legendHint{opacity:.8}._6VtqdG_badgeSource,._6VtqdG_badgeCount{opacity:.72;background:#8080801a;border:none}._6VtqdG_badgeDisabled{opacity:.58;background:#8080801a;border:none}._6VtqdG_disclosure{min-width:0;color:inherit;cursor:pointer;text-align:left;background:0 0;border:none;flex:1;align-items:center;gap:7px;padding:2px 0;display:flex}._6VtqdG_disclosure:hover ._6VtqdG_groupTitle{opacity:1}._6VtqdG_chevron{opacity:.55;border-bottom:1.5px solid;border-right:1.5px solid;flex:none;width:8px;height:8px;margin-right:2px;transition:transform .15s;transform:rotate(45deg)}._6VtqdG_chevronCollapsed{transform:rotate(-45deg)}._6VtqdG_headerCount{opacity:.5;white-space:nowrap;margin-left:2px;font-size:12px}._6VtqdG_subbar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_legendToggle{width:22px;height:22px;color:inherit;cursor:pointer;opacity:.55;background:#80808024;border:none;border-radius:50%;flex:none;font-size:12px;line-height:1;transition:opacity .15s,background .15s}._6VtqdG_legendToggle:hover{opacity:1}._6VtqdG_legendToggleActive{opacity:1;background:#80808047}._6VtqdG_filterBar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_filterBar ._6VtqdG_formLabel{margin:0}._6VtqdG_useCount{color:#d29922;vertical-align:1px;background:#d299221f;border-radius:999px;margin-left:6px;padding:0 6px;font-size:11px;font-weight:700;line-height:1.6;display:inline-block}._6VtqdG_useTime{opacity:.45;white-space:nowrap;margin-left:auto;font-size:11px}._6VtqdG_switchMixed{background:#2f81f773}._6VtqdG_switchMixed ._6VtqdG_switchThumb{transform:translate(7.5px);box-shadow:0 1px 2px #00000040}._6VtqdG_groupTitleInner{align-items:baseline;display:inline-flex}._6VtqdG_groupOps{flex-wrap:wrap;flex:none;align-items:center;gap:6px;display:inline-flex}._6VtqdG_sourceLink{color:inherit;opacity:.9;border-bottom:1px dotted;font-weight:600;text-decoration:none}._6VtqdG_sourceLink:hover{opacity:1}._6VtqdG_statusBadges{flex:none;align-items:center;gap:4px;display:inline-flex}._6VtqdG_statusBadge{opacity:.8;white-space:nowrap;background:#8080801a;border:.5px solid #8080804d;border-radius:999px;padding:1px 7px;font-size:10.5px}._6VtqdG_statusOk{color:#3fb950;background:0 0;border-color:currentColor}._6VtqdG_statusUpdated{color:#d29922;background:#d2992214;border-color:currentColor}._6VtqdG_statusError{color:#d1242f;background:#d1242f14;border-color:currentColor}._6VtqdG_statusWrap{cursor:pointer;background:0 0;border:none;align-items:center;gap:4px;padding:0;font-family:inherit;display:inline-flex}._6VtqdG_statusWrap:hover ._6VtqdG_statusBadge{opacity:1}._6VtqdG_statusButton{cursor:pointer;font-family:inherit}._6VtqdG_statusButton:hover{opacity:1}._6VtqdG_opDanger{color:#d1242f;background:#d1242f1a}._6VtqdG_opDanger:hover{background:#d1242f2e}._6VtqdG_iconBtn{border-radius:999px;justify-content:center;align-items:center;width:24px;height:24px;padding:0;display:inline-flex}._6VtqdG_sourceCard{background:#8080800d;border-radius:12px;flex-direction:column;gap:4px;padding:12px 14px;font-size:12px;display:flex}._6VtqdG_sourceCardTitle{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_dialogOverlay{z-index:50;-webkit-backdrop-filter:blur(2px);background:#00000059;justify-content:center;align-items:center;padding:24px;display:flex;position:fixed;inset:0}._6VtqdG_dialog{background:var(--dsw-surface,#f5f5f7);min-width:min(320px,100%);max-width:380px;color:var(--dsw-text,#1d1d1f);border-radius:14px;padding:18px 18px 14px;box-shadow:0 12px 40px #00000047,0 2px 8px #00000029}@media (prefers-color-scheme:dark){._6VtqdG_dialog{color:#f5f5f7;background:#2c2c2e}}._6VtqdG_dialogTitle{letter-spacing:-.01em;margin:0 0 8px;font-size:15px;font-weight:700}._6VtqdG_dialogText{opacity:.65;margin:0 0 10px;font-size:12.5px;line-height:1.5}._6VtqdG_dialogList{flex-direction:column;gap:5px;max-height:200px;margin:0 0 14px;padding:0;list-style:none;display:flex;overflow:auto}._6VtqdG_dialogList li{opacity:.85;word-break:break-all;font-size:12.5px}._6VtqdG_dialogActions{justify-content:flex-end;gap:8px;display:flex}._6VtqdG_filterBar ._6VtqdG_search{flex:1;min-width:160px}._6VtqdG_filterBar ._6VtqdG_formLabel{flex:none}._6VtqdG_filterBar ._6VtqdG_select{flex:none;max-width:150px}._6VtqdG_filterBar ._6VtqdG_segmented{flex:none}._6VtqdG_titleIcon{vertical-align:-2px;opacity:.8;margin-right:2px;display:inline-block}._6VtqdG_grow{flex:1}._6VtqdG_hintLine{opacity:.55;margin:0;font-size:11.5px}._6VtqdG_hintPadded{margin:6px 12px 2px}._6VtqdG_hintInline{margin:4px 2px}._6VtqdG_rowMuted{cursor:default;opacity:.55}._6VtqdG_actionsPadded{padding:8px 12px}._6VtqdG_actionsTop{margin-top:8px}._6VtqdG_actionsBottom{margin-bottom:8px}._6VtqdG_groupTime{margin-left:6px}._6VtqdG_sectionHeadRow{align-items:center;gap:8px;display:flex}._6VtqdG_sectionTitleFill{flex:1}._6VtqdG_dialogSelect{width:100%;margin-bottom:12px}._6VtqdG_dialogRow{align-items:center;gap:8px;display:flex}";
|
|
844
1587
|
const tagId = "dsh-skill-hub/panel.module.css";
|
|
845
1588
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
846
1589
|
const tag = document.createElement("style");
|
|
@@ -851,16 +1594,24 @@ window.__ModuleLoader__.load({
|
|
|
851
1594
|
}
|
|
852
1595
|
var panel_module_css_default = {
|
|
853
1596
|
"actions": "_6VtqdG_actions",
|
|
1597
|
+
"actionsBottom": "_6VtqdG_actionsBottom",
|
|
1598
|
+
"actionsPadded": "_6VtqdG_actionsPadded",
|
|
1599
|
+
"actionsTop": "_6VtqdG_actionsTop",
|
|
854
1600
|
"back": "_6VtqdG_back",
|
|
855
1601
|
"badge": "_6VtqdG_badge",
|
|
1602
|
+
"badgeCount": "_6VtqdG_badgeCount",
|
|
1603
|
+
"badgeDisabled": "_6VtqdG_badgeDisabled",
|
|
856
1604
|
"badgeModel": "_6VtqdG_badgeModel",
|
|
857
1605
|
"badgeReadonly": "_6VtqdG_badgeReadonly",
|
|
1606
|
+
"badgeSource": "_6VtqdG_badgeSource",
|
|
858
1607
|
"badgeUser": "_6VtqdG_badgeUser",
|
|
859
1608
|
"badgeUses": "_6VtqdG_badgeUses",
|
|
860
1609
|
"badges": "_6VtqdG_badges",
|
|
861
1610
|
"button": "_6VtqdG_button",
|
|
862
1611
|
"buttons": "_6VtqdG_buttons",
|
|
863
|
-
"
|
|
1612
|
+
"chevron": "_6VtqdG_chevron",
|
|
1613
|
+
"chevronCollapsed": "_6VtqdG_chevronCollapsed",
|
|
1614
|
+
"danger": "_6VtqdG_danger",
|
|
864
1615
|
"detailContent": "_6VtqdG_detailContent",
|
|
865
1616
|
"detailHead": "_6VtqdG_detailHead",
|
|
866
1617
|
"detailMeta": "_6VtqdG_detailMeta",
|
|
@@ -869,78 +1620,1481 @@ window.__ModuleLoader__.load({
|
|
|
869
1620
|
"diagPath": "_6VtqdG_diagPath",
|
|
870
1621
|
"diagReason": "_6VtqdG_diagReason",
|
|
871
1622
|
"diagRow": "_6VtqdG_diagRow",
|
|
1623
|
+
"dialog": "_6VtqdG_dialog",
|
|
1624
|
+
"dialogActions": "_6VtqdG_dialogActions",
|
|
1625
|
+
"dialogList": "_6VtqdG_dialogList",
|
|
1626
|
+
"dialogOverlay": "_6VtqdG_dialogOverlay",
|
|
1627
|
+
"dialogRow": "_6VtqdG_dialogRow",
|
|
1628
|
+
"dialogSelect": "_6VtqdG_dialogSelect",
|
|
1629
|
+
"dialogText": "_6VtqdG_dialogText",
|
|
1630
|
+
"dialogTitle": "_6VtqdG_dialogTitle",
|
|
1631
|
+
"disclosure": "_6VtqdG_disclosure",
|
|
1632
|
+
"dot": "_6VtqdG_dot",
|
|
1633
|
+
"dotModel": "_6VtqdG_dotModel",
|
|
1634
|
+
"dotUser": "_6VtqdG_dotUser",
|
|
872
1635
|
"empty": "_6VtqdG_empty",
|
|
873
1636
|
"errorBanner": "_6VtqdG_errorBanner",
|
|
1637
|
+
"filterBar": "_6VtqdG_filterBar",
|
|
874
1638
|
"form": "_6VtqdG_form",
|
|
875
1639
|
"formError": "_6VtqdG_formError",
|
|
876
1640
|
"formLabel": "_6VtqdG_formLabel",
|
|
877
1641
|
"formRow": "_6VtqdG_formRow",
|
|
878
1642
|
"formSuccess": "_6VtqdG_formSuccess",
|
|
1643
|
+
"groupHead": "_6VtqdG_groupHead",
|
|
1644
|
+
"groupOps": "_6VtqdG_groupOps",
|
|
1645
|
+
"groupTime": "_6VtqdG_groupTime",
|
|
879
1646
|
"groupTitle": "_6VtqdG_groupTitle",
|
|
1647
|
+
"groupTitleInner": "_6VtqdG_groupTitleInner",
|
|
1648
|
+
"grow": "_6VtqdG_grow",
|
|
880
1649
|
"header": "_6VtqdG_header",
|
|
1650
|
+
"headerCount": "_6VtqdG_headerCount",
|
|
881
1651
|
"hint": "_6VtqdG_hint",
|
|
1652
|
+
"hintInline": "_6VtqdG_hintInline",
|
|
1653
|
+
"hintLine": "_6VtqdG_hintLine",
|
|
1654
|
+
"hintPadded": "_6VtqdG_hintPadded",
|
|
1655
|
+
"iconBtn": "_6VtqdG_iconBtn",
|
|
882
1656
|
"input": "_6VtqdG_input",
|
|
1657
|
+
"legend": "_6VtqdG_legend",
|
|
1658
|
+
"legendHint": "_6VtqdG_legendHint",
|
|
1659
|
+
"legendItem": "_6VtqdG_legendItem",
|
|
1660
|
+
"legendToggle": "_6VtqdG_legendToggle",
|
|
1661
|
+
"legendToggleActive": "_6VtqdG_legendToggleActive",
|
|
883
1662
|
"muted": "_6VtqdG_muted",
|
|
1663
|
+
"opBtn": "_6VtqdG_opBtn",
|
|
1664
|
+
"opDanger": "_6VtqdG_opDanger",
|
|
884
1665
|
"panel": "_6VtqdG_panel",
|
|
885
1666
|
"primary": "_6VtqdG_primary",
|
|
886
1667
|
"row": "_6VtqdG_row",
|
|
887
1668
|
"rowDesc": "_6VtqdG_rowDesc",
|
|
888
1669
|
"rowMain": "_6VtqdG_rowMain",
|
|
1670
|
+
"rowMeta": "_6VtqdG_rowMeta",
|
|
1671
|
+
"rowMuted": "_6VtqdG_rowMuted",
|
|
889
1672
|
"rowName": "_6VtqdG_rowName",
|
|
1673
|
+
"rowNameText": "_6VtqdG_rowNameText",
|
|
1674
|
+
"rowStatic": "_6VtqdG_rowStatic",
|
|
890
1675
|
"search": "_6VtqdG_search",
|
|
891
1676
|
"section": "_6VtqdG_section",
|
|
1677
|
+
"sectionHeadRow": "_6VtqdG_sectionHeadRow",
|
|
892
1678
|
"sectionTitle": "_6VtqdG_sectionTitle",
|
|
1679
|
+
"sectionTitleFill": "_6VtqdG_sectionTitleFill",
|
|
893
1680
|
"segBtn": "_6VtqdG_segBtn",
|
|
894
1681
|
"segBtnActive": "_6VtqdG_segBtnActive",
|
|
895
1682
|
"segmented": "_6VtqdG_segmented",
|
|
896
1683
|
"select": "_6VtqdG_select",
|
|
897
|
-
"
|
|
898
|
-
"
|
|
1684
|
+
"sourceCard": "_6VtqdG_sourceCard",
|
|
1685
|
+
"sourceCardTitle": "_6VtqdG_sourceCardTitle",
|
|
1686
|
+
"sourceLink": "_6VtqdG_sourceLink",
|
|
1687
|
+
"statusBadge": "_6VtqdG_statusBadge",
|
|
1688
|
+
"statusBadges": "_6VtqdG_statusBadges",
|
|
1689
|
+
"statusButton": "_6VtqdG_statusButton",
|
|
1690
|
+
"statusError": "_6VtqdG_statusError",
|
|
1691
|
+
"statusOk": "_6VtqdG_statusOk",
|
|
1692
|
+
"statusUpdated": "_6VtqdG_statusUpdated",
|
|
1693
|
+
"statusWrap": "_6VtqdG_statusWrap",
|
|
1694
|
+
"subbar": "_6VtqdG_subbar",
|
|
1695
|
+
"successBanner": "_6VtqdG_successBanner",
|
|
1696
|
+
"switch": "_6VtqdG_switch",
|
|
1697
|
+
"switchMixed": "_6VtqdG_switchMixed",
|
|
899
1698
|
"switchOn": "_6VtqdG_switchOn",
|
|
900
|
-
"
|
|
1699
|
+
"switchThumb": "_6VtqdG_switchThumb",
|
|
1700
|
+
"title": "_6VtqdG_title",
|
|
1701
|
+
"titleIcon": "_6VtqdG_titleIcon",
|
|
1702
|
+
"updateLink": "_6VtqdG_updateLink",
|
|
1703
|
+
"useCount": "_6VtqdG_useCount",
|
|
1704
|
+
"useTime": "_6VtqdG_useTime"
|
|
901
1705
|
};
|
|
902
1706
|
//#endregion
|
|
903
|
-
//#region src/client/panel/
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1707
|
+
//#region src/client/panel/SourceStatusBadge.tsx
|
|
1708
|
+
function SourceStatusBadge(props) {
|
|
1709
|
+
const { check, checking, onCheck } = props;
|
|
1710
|
+
if (checking === true) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1711
|
+
className: panel_module_css_default.statusBadge,
|
|
1712
|
+
children: tt("source.checking")
|
|
1713
|
+
});
|
|
1714
|
+
if (check === void 0) {
|
|
1715
|
+
if (onCheck === void 0) return null;
|
|
1716
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1717
|
+
type: "button",
|
|
1718
|
+
className: panel_module_css_default.statusBadge + " " + panel_module_css_default.statusButton,
|
|
1719
|
+
title: tt("source.checkHint"),
|
|
1720
|
+
onClick: onCheck,
|
|
1721
|
+
children: tt("source.check")
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
const badges = (() => {
|
|
1725
|
+
if (check.error !== void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1726
|
+
className: panel_module_css_default.statusBadge + " " + panel_module_css_default.statusError,
|
|
1727
|
+
children: tt("source.error")
|
|
1728
|
+
});
|
|
1729
|
+
if (check.throttled === true) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1730
|
+
className: panel_module_css_default.statusBadge,
|
|
1731
|
+
children: tt("source.throttled")
|
|
1732
|
+
});
|
|
1733
|
+
if (check.unverified === true) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1734
|
+
className: panel_module_css_default.statusBadge,
|
|
1735
|
+
children: tt("source.unverified")
|
|
1736
|
+
});
|
|
1737
|
+
if (check.changed) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1738
|
+
className: panel_module_css_default.statusBadges,
|
|
1739
|
+
children: [check.updated.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1740
|
+
className: panel_module_css_default.statusBadge + " " + panel_module_css_default.statusUpdated,
|
|
1741
|
+
children: tt("source.updated", { count: check.updated.length })
|
|
1742
|
+
}) : null, check.deleted.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1743
|
+
className: panel_module_css_default.statusBadge + " " + panel_module_css_default.statusError,
|
|
1744
|
+
children: tt("source.deleted", { count: check.deleted.length })
|
|
1745
|
+
}) : null]
|
|
1746
|
+
});
|
|
1747
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1748
|
+
className: panel_module_css_default.statusBadge + " " + panel_module_css_default.statusOk,
|
|
1749
|
+
children: tt("source.upToDate")
|
|
1750
|
+
});
|
|
1751
|
+
})();
|
|
1752
|
+
if (onCheck === void 0) return badges;
|
|
1753
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1754
|
+
type: "button",
|
|
1755
|
+
className: panel_module_css_default.statusWrap,
|
|
1756
|
+
title: tt("source.checkHint"),
|
|
1757
|
+
onClick: onCheck,
|
|
1758
|
+
children: badges
|
|
1759
|
+
});
|
|
923
1760
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
function
|
|
927
|
-
const {
|
|
928
|
-
const
|
|
929
|
-
const
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1761
|
+
//#endregion
|
|
1762
|
+
//#region src/client/panel/SkillDetailView.tsx
|
|
1763
|
+
function SkillDetailView(props) {
|
|
1764
|
+
const { detail, hubConfig, uses, groupsState, sourcesState, sourceCheck, checkingSource, syncingSource, loading, onBack, onCheck, onSync, onFollowDelete } = props;
|
|
1765
|
+
const detailSource = sourcesState?.sources.find((source) => source.skills.includes(detail.name));
|
|
1766
|
+
const detailCheck = detailSource !== void 0 ? sourceCheck[detailSource.repo] : void 0;
|
|
1767
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1768
|
+
className: panel_module_css_default.panel,
|
|
1769
|
+
children: [
|
|
1770
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1771
|
+
className: panel_module_css_default.detailHead,
|
|
1772
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1773
|
+
type: "button",
|
|
1774
|
+
className: panel_module_css_default.back,
|
|
1775
|
+
onClick: onBack,
|
|
1776
|
+
children: tt("detail.back")
|
|
1777
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1778
|
+
className: panel_module_css_default.detailName,
|
|
1779
|
+
children: [
|
|
1780
|
+
detail.name,
|
|
1781
|
+
detail.invocation.modelInvocable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1782
|
+
className: panel_module_css_default.dot + " " + panel_module_css_default.dotModel,
|
|
1783
|
+
style: dotStyle(hubConfig?.dotModelColor),
|
|
1784
|
+
title: tt("legend.model")
|
|
1785
|
+
}) : null,
|
|
1786
|
+
detail.invocation.userInvocable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1787
|
+
className: panel_module_css_default.dot + " " + panel_module_css_default.dotUser,
|
|
1788
|
+
style: dotStyle(hubConfig?.dotUserColor),
|
|
1789
|
+
title: tt("legend.user")
|
|
1790
|
+
}) : null
|
|
1791
|
+
]
|
|
1792
|
+
})]
|
|
1793
|
+
}),
|
|
1794
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1795
|
+
className: panel_module_css_default.detailMeta,
|
|
1796
|
+
children: [
|
|
1797
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1798
|
+
className: panel_module_css_default.detailMetaLine,
|
|
1799
|
+
children: [
|
|
1800
|
+
tt("detail.provider"),
|
|
1801
|
+
": ",
|
|
1802
|
+
detail.provider
|
|
1803
|
+
]
|
|
1804
|
+
}),
|
|
1805
|
+
detail.addedAt !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1806
|
+
className: panel_module_css_default.detailMetaLine,
|
|
1807
|
+
children: [
|
|
1808
|
+
tt("detail.addedAt"),
|
|
1809
|
+
": ",
|
|
1810
|
+
formatDateTime(detail.addedAt)
|
|
1811
|
+
]
|
|
1812
|
+
}) : null,
|
|
1813
|
+
detail.updatedAt !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1814
|
+
className: panel_module_css_default.detailMetaLine,
|
|
1815
|
+
children: [
|
|
1816
|
+
tt("detail.updatedAt"),
|
|
1817
|
+
": ",
|
|
1818
|
+
formatDateTime(detail.updatedAt)
|
|
1819
|
+
]
|
|
1820
|
+
}) : null,
|
|
1821
|
+
detail.path !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1822
|
+
className: panel_module_css_default.detailMetaLine,
|
|
1823
|
+
children: [
|
|
1824
|
+
tt("detail.path"),
|
|
1825
|
+
": ",
|
|
1826
|
+
detail.path
|
|
1827
|
+
]
|
|
1828
|
+
}) : null,
|
|
1829
|
+
detail.whenToUse !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1830
|
+
className: panel_module_css_default.detailMetaLine,
|
|
1831
|
+
children: [
|
|
1832
|
+
tt("detail.whenToUse"),
|
|
1833
|
+
": ",
|
|
1834
|
+
detail.whenToUse
|
|
1835
|
+
]
|
|
1836
|
+
}) : null,
|
|
1837
|
+
(() => {
|
|
1838
|
+
const stat = uses.get(detail.name);
|
|
1839
|
+
if (stat === void 0 || stat.count === 0) return null;
|
|
1840
|
+
const at = stat.lastUsed !== void 0 ? " · " + new Date(stat.lastUsed).toLocaleString() : "";
|
|
1841
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1842
|
+
className: panel_module_css_default.detailMetaLine,
|
|
1843
|
+
children: [
|
|
1844
|
+
tt("detail.uses"),
|
|
1845
|
+
": ",
|
|
1846
|
+
stat.count,
|
|
1847
|
+
at
|
|
1848
|
+
]
|
|
1849
|
+
});
|
|
1850
|
+
})(),
|
|
1851
|
+
(() => {
|
|
1852
|
+
const tags = (groupsState?.tags ?? []).filter((tag) => tag.skillNames.includes(detail.name)).map((tag) => tag.name);
|
|
1853
|
+
return tags.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1854
|
+
className: panel_module_css_default.detailMetaLine,
|
|
1855
|
+
children: [
|
|
1856
|
+
tt("detail.groups"),
|
|
1857
|
+
": ",
|
|
1858
|
+
tags.join(", ")
|
|
1859
|
+
]
|
|
1860
|
+
}) : null;
|
|
1861
|
+
})()
|
|
1862
|
+
]
|
|
1863
|
+
}),
|
|
1864
|
+
detailSource !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1865
|
+
className: panel_module_css_default.sourceCard,
|
|
1866
|
+
children: [
|
|
1867
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1868
|
+
className: panel_module_css_default.sourceCardTitle,
|
|
1869
|
+
children: [
|
|
1870
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
1871
|
+
className: panel_module_css_default.sourceLink,
|
|
1872
|
+
href: "https://github.com/" + detailSource.repo,
|
|
1873
|
+
target: "_blank",
|
|
1874
|
+
rel: "noreferrer",
|
|
1875
|
+
children: detailSource.repo
|
|
1876
|
+
}),
|
|
1877
|
+
detailSource.ref !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1878
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeSource,
|
|
1879
|
+
children: detailSource.ref
|
|
1880
|
+
}) : null,
|
|
1881
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SourceStatusBadge, { check: detailCheck })
|
|
1882
|
+
]
|
|
1883
|
+
}),
|
|
1884
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1885
|
+
className: panel_module_css_default.detailMetaLine,
|
|
1886
|
+
children: [
|
|
1887
|
+
tt("source.commit"),
|
|
1888
|
+
": ",
|
|
1889
|
+
detailSource.commitSha === "" ? tt("source.unverified") : shortSha(detailSource.commitSha)
|
|
1890
|
+
]
|
|
1891
|
+
}),
|
|
1892
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1893
|
+
className: panel_module_css_default.buttons + " " + panel_module_css_default.actionsTop,
|
|
1894
|
+
children: [
|
|
1895
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1896
|
+
type: "button",
|
|
1897
|
+
className: panel_module_css_default.opBtn,
|
|
1898
|
+
disabled: checkingSource !== null,
|
|
1899
|
+
onClick: () => {
|
|
1900
|
+
onCheck(detailSource.repo);
|
|
1901
|
+
},
|
|
1902
|
+
children: checkingSource === detailSource.repo ? tt("source.checking") : tt("source.check")
|
|
1903
|
+
}),
|
|
1904
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1905
|
+
type: "button",
|
|
1906
|
+
className: panel_module_css_default.opBtn,
|
|
1907
|
+
disabled: syncingSource !== null,
|
|
1908
|
+
onClick: () => {
|
|
1909
|
+
onSync(detailSource.repo, [detail.name]);
|
|
1910
|
+
},
|
|
1911
|
+
children: syncingSource === detailSource.repo ? tt("source.syncing") : tt("source.sync")
|
|
1912
|
+
}),
|
|
1913
|
+
detailCheck?.deleted.includes(detail.name) === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1914
|
+
type: "button",
|
|
1915
|
+
className: panel_module_css_default.opBtn + " " + panel_module_css_default.opDanger,
|
|
1916
|
+
onClick: () => {
|
|
1917
|
+
onFollowDelete(detailSource.repo, [detail.name]);
|
|
1918
|
+
},
|
|
1919
|
+
children: tt("source.followDelete")
|
|
1920
|
+
}) : null
|
|
1921
|
+
]
|
|
1922
|
+
})
|
|
1923
|
+
]
|
|
1924
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1925
|
+
className: panel_module_css_default.hintLine,
|
|
1926
|
+
children: tt("source.private")
|
|
1927
|
+
}),
|
|
1928
|
+
loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1929
|
+
className: panel_module_css_default.muted,
|
|
1930
|
+
children: tt("detail.loading")
|
|
1931
|
+
}) : null,
|
|
1932
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
1933
|
+
className: panel_module_css_default.detailContent,
|
|
1934
|
+
children: detail.content
|
|
1935
|
+
})
|
|
1936
|
+
]
|
|
1937
|
+
});
|
|
1938
|
+
}
|
|
1939
|
+
//#endregion
|
|
1940
|
+
//#region src/client/panel/TagEditorView.tsx
|
|
1941
|
+
function TagEditorView(props) {
|
|
1942
|
+
const { tag, editName, membersDraft, editSearch, catalog, tagBusy, onBack, onEditName, onEditSearch, onToggleMember, onRename, onDelete, onSaveMembers } = props;
|
|
1943
|
+
const editQuery = editSearch.trim().toLocaleLowerCase();
|
|
1944
|
+
const editSkills = (catalog?.skills ?? []).filter((skill) => editQuery.length === 0 || skill.name.toLocaleLowerCase().includes(editQuery) || skill.description.toLocaleLowerCase().includes(editQuery));
|
|
1945
|
+
const editDisabled = (catalog?.disabled ?? []).filter((record) => editQuery.length === 0 || record.name.toLocaleLowerCase().includes(editQuery) || record.description.toLocaleLowerCase().includes(editQuery));
|
|
1946
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1947
|
+
className: panel_module_css_default.panel,
|
|
1948
|
+
children: [
|
|
1949
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1950
|
+
className: panel_module_css_default.detailHead,
|
|
1951
|
+
children: [
|
|
1952
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1953
|
+
type: "button",
|
|
1954
|
+
className: panel_module_css_default.back,
|
|
1955
|
+
onClick: onBack,
|
|
1956
|
+
children: tt("detail.back")
|
|
1957
|
+
}),
|
|
1958
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1959
|
+
className: panel_module_css_default.input + " " + panel_module_css_default.grow,
|
|
1960
|
+
value: editName,
|
|
1961
|
+
onChange: (event) => {
|
|
1962
|
+
onEditName(event.target.value);
|
|
1963
|
+
},
|
|
1964
|
+
placeholder: tt("groups.namePlaceholder")
|
|
1965
|
+
}),
|
|
1966
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1967
|
+
type: "button",
|
|
1968
|
+
className: panel_module_css_default.opBtn,
|
|
1969
|
+
disabled: tagBusy || editName.trim() === "",
|
|
1970
|
+
onClick: onRename,
|
|
1971
|
+
children: tt("groups.rename")
|
|
1972
|
+
}),
|
|
1973
|
+
tag.default !== true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1974
|
+
type: "button",
|
|
1975
|
+
className: panel_module_css_default.opBtn,
|
|
1976
|
+
disabled: tagBusy,
|
|
1977
|
+
onClick: onDelete,
|
|
1978
|
+
children: tt("groups.delete")
|
|
1979
|
+
}) : null
|
|
1980
|
+
]
|
|
1981
|
+
}),
|
|
1982
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1983
|
+
className: panel_module_css_default.hintLine,
|
|
1984
|
+
children: tt("groups.membersHint")
|
|
1985
|
+
}),
|
|
1986
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1987
|
+
className: panel_module_css_default.search,
|
|
1988
|
+
value: editSearch,
|
|
1989
|
+
onChange: (event) => {
|
|
1990
|
+
onEditSearch(event.target.value);
|
|
1991
|
+
},
|
|
1992
|
+
placeholder: tt("panel.search")
|
|
1993
|
+
}),
|
|
1994
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1995
|
+
className: panel_module_css_default.section,
|
|
1996
|
+
children: [editSkills.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1997
|
+
className: panel_module_css_default.row,
|
|
1998
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1999
|
+
type: "checkbox",
|
|
2000
|
+
checked: membersDraft.has(skill.name),
|
|
2001
|
+
onChange: (event) => {
|
|
2002
|
+
onToggleMember(skill.name, event.target.checked);
|
|
2003
|
+
}
|
|
2004
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2005
|
+
className: panel_module_css_default.rowMain,
|
|
2006
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2007
|
+
className: panel_module_css_default.rowName,
|
|
2008
|
+
children: skill.name
|
|
2009
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2010
|
+
className: panel_module_css_default.rowDesc,
|
|
2011
|
+
children: skill.description
|
|
2012
|
+
})]
|
|
2013
|
+
})]
|
|
2014
|
+
}, skill.name)), editDisabled.map((record) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2015
|
+
className: panel_module_css_default.row,
|
|
2016
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2017
|
+
type: "checkbox",
|
|
2018
|
+
checked: membersDraft.has(record.name),
|
|
2019
|
+
onChange: (event) => {
|
|
2020
|
+
onToggleMember(record.name, event.target.checked);
|
|
2021
|
+
}
|
|
2022
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2023
|
+
className: panel_module_css_default.rowMain,
|
|
2024
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2025
|
+
className: panel_module_css_default.rowName,
|
|
2026
|
+
children: record.name
|
|
2027
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2028
|
+
className: panel_module_css_default.rowDesc,
|
|
2029
|
+
children: [
|
|
2030
|
+
record.description,
|
|
2031
|
+
" · ",
|
|
2032
|
+
tt("panel.disabled")
|
|
2033
|
+
]
|
|
2034
|
+
})]
|
|
2035
|
+
})]
|
|
2036
|
+
}, record.name))]
|
|
2037
|
+
}),
|
|
2038
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2039
|
+
className: panel_module_css_default.buttons,
|
|
2040
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2041
|
+
type: "button",
|
|
2042
|
+
className: panel_module_css_default.button + " " + panel_module_css_default.primary,
|
|
2043
|
+
disabled: tagBusy,
|
|
2044
|
+
onClick: onSaveMembers,
|
|
2045
|
+
children: tt("groups.saveMembers")
|
|
2046
|
+
})
|
|
2047
|
+
})
|
|
2048
|
+
]
|
|
2049
|
+
});
|
|
2050
|
+
}
|
|
2051
|
+
//#endregion
|
|
2052
|
+
//#region src/client/panel/SkillRow.tsx
|
|
2053
|
+
function SkillRow(props) {
|
|
2054
|
+
const { skill, hub } = props;
|
|
2055
|
+
const stat = hub.uses.get(skill.name);
|
|
2056
|
+
const count = stat?.count ?? 0;
|
|
2057
|
+
const lastUsed = stat?.lastUsed;
|
|
2058
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2059
|
+
className: panel_module_css_default.row,
|
|
2060
|
+
onClick: () => {
|
|
2061
|
+
hub.openDetail(skill.name);
|
|
2062
|
+
},
|
|
2063
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2064
|
+
className: panel_module_css_default.rowMain,
|
|
2065
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2066
|
+
className: panel_module_css_default.rowName,
|
|
2067
|
+
children: [
|
|
2068
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2069
|
+
className: panel_module_css_default.rowNameText,
|
|
2070
|
+
children: skill.name
|
|
2071
|
+
}),
|
|
2072
|
+
count > 0 && hub.hubConfig?.showUseCount !== false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2073
|
+
className: panel_module_css_default.useCount,
|
|
2074
|
+
children: count
|
|
2075
|
+
}) : null,
|
|
2076
|
+
skill.invocation.modelInvocable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2077
|
+
className: panel_module_css_default.dot + " " + panel_module_css_default.dotModel,
|
|
2078
|
+
style: dotStyle(hub.hubConfig?.dotModelColor),
|
|
2079
|
+
title: tt("legend.model")
|
|
2080
|
+
}) : null,
|
|
2081
|
+
skill.invocation.userInvocable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2082
|
+
className: panel_module_css_default.dot + " " + panel_module_css_default.dotUser,
|
|
2083
|
+
style: dotStyle(hub.hubConfig?.dotUserColor),
|
|
2084
|
+
title: tt("legend.user")
|
|
2085
|
+
}) : null,
|
|
2086
|
+
lastUsed !== void 0 && hub.hubConfig?.showUseTime !== false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2087
|
+
className: panel_module_css_default.useTime,
|
|
2088
|
+
children: relativeTimeText(lastUsed)
|
|
2089
|
+
}) : null
|
|
2090
|
+
]
|
|
2091
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2092
|
+
className: panel_module_css_default.rowDesc,
|
|
2093
|
+
children: skill.description
|
|
2094
|
+
})]
|
|
2095
|
+
}), skill.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2096
|
+
type: "button",
|
|
2097
|
+
className: panel_module_css_default.opBtn + " " + panel_module_css_default.opDanger + " " + panel_module_css_default.iconBtn,
|
|
2098
|
+
disabled: hub.busyNames.has(skill.name) || hub.tagBusy,
|
|
2099
|
+
title: tt("row.delete"),
|
|
2100
|
+
"aria-label": tt("row.delete"),
|
|
2101
|
+
onClick: (event) => {
|
|
2102
|
+
event.stopPropagation();
|
|
2103
|
+
hub.requestDeleteSkill(skill.name);
|
|
2104
|
+
},
|
|
2105
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
|
|
2106
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2107
|
+
type: "button",
|
|
2108
|
+
role: "switch",
|
|
2109
|
+
"aria-checked": true,
|
|
2110
|
+
"aria-label": tt("row.disable"),
|
|
2111
|
+
className: panel_module_css_default.switch + " " + panel_module_css_default.switchOn,
|
|
2112
|
+
disabled: hub.busyNames.has(skill.name),
|
|
2113
|
+
onClick: (event) => {
|
|
2114
|
+
event.stopPropagation();
|
|
2115
|
+
hub.toggle(skill, false);
|
|
2116
|
+
},
|
|
2117
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.switchThumb })
|
|
2118
|
+
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2119
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeReadonly,
|
|
2120
|
+
children: tt("row.readonly")
|
|
2121
|
+
})]
|
|
2122
|
+
});
|
|
2123
|
+
}
|
|
2124
|
+
//#endregion
|
|
2125
|
+
//#region src/client/panel/DisabledRow.tsx
|
|
2126
|
+
/** Source badge label for a disabled record, derived from its writable root. */
|
|
2127
|
+
function disabledSourceLabel(root) {
|
|
2128
|
+
return tt("badge.source." + root);
|
|
2129
|
+
}
|
|
2130
|
+
function DisabledRow(props) {
|
|
2131
|
+
const { record, busy, onEnable } = props;
|
|
2132
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2133
|
+
className: panel_module_css_default.row + " " + panel_module_css_default.rowStatic,
|
|
2134
|
+
children: [
|
|
2135
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2136
|
+
className: panel_module_css_default.rowMain,
|
|
2137
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2138
|
+
className: panel_module_css_default.rowName,
|
|
2139
|
+
children: record.name
|
|
2140
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2141
|
+
className: panel_module_css_default.rowDesc,
|
|
2142
|
+
children: record.description
|
|
2143
|
+
})]
|
|
2144
|
+
}),
|
|
2145
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2146
|
+
className: panel_module_css_default.badges,
|
|
2147
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2148
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeSource,
|
|
2149
|
+
children: disabledSourceLabel(record.root)
|
|
2150
|
+
})
|
|
2151
|
+
}),
|
|
2152
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2153
|
+
type: "button",
|
|
2154
|
+
role: "switch",
|
|
2155
|
+
"aria-checked": false,
|
|
2156
|
+
"aria-label": tt("row.enable"),
|
|
2157
|
+
className: panel_module_css_default.switch,
|
|
2158
|
+
disabled: busy,
|
|
2159
|
+
onClick: onEnable,
|
|
2160
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.switchThumb })
|
|
2161
|
+
})
|
|
2162
|
+
]
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
//#endregion
|
|
2166
|
+
//#region src/client/panel/GroupSummary.tsx
|
|
2167
|
+
function GroupSummary(props) {
|
|
2168
|
+
const { members, hub } = props;
|
|
2169
|
+
let total = 0;
|
|
2170
|
+
let latest;
|
|
2171
|
+
for (const name of members) {
|
|
2172
|
+
const stat = hub.uses.get(name);
|
|
2173
|
+
if (stat === void 0) continue;
|
|
2174
|
+
total += stat.count;
|
|
2175
|
+
if (stat.lastUsed !== void 0 && (latest === void 0 || stat.lastUsed > latest)) latest = stat.lastUsed;
|
|
2176
|
+
}
|
|
2177
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2178
|
+
className: panel_module_css_default.groupTitleInner,
|
|
2179
|
+
children: [hub.hubConfig?.showGroupSummary !== false && total > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2180
|
+
className: panel_module_css_default.useCount,
|
|
2181
|
+
children: total
|
|
2182
|
+
}) : null, hub.hubConfig?.showGroupSummary !== false && latest !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2183
|
+
className: panel_module_css_default.useTime + " " + panel_module_css_default.groupTime,
|
|
2184
|
+
children: relativeTimeText(latest)
|
|
2185
|
+
}) : null]
|
|
2186
|
+
});
|
|
2187
|
+
}
|
|
2188
|
+
//#endregion
|
|
2189
|
+
//#region src/client/panel/SourcesView.tsx
|
|
2190
|
+
function SourcesView(props) {
|
|
2191
|
+
const { hub } = props;
|
|
2192
|
+
const { catalog, groupsState, skillView, sourceFilter, origins, sorted, normalized, collapsedGroups, viewNames, sourceCheck, actionNames, checkingSource, syncingSource, batchBusy, busyNames, toggleGroupCollapse, checkSources, requestSync, requestDelete, toggleGroup, enableDisabled } = hub;
|
|
2193
|
+
if (skillView === "flat") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: filterBySource(sorted, sourceFilter, origins).map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillRow, {
|
|
2194
|
+
skill,
|
|
2195
|
+
hub
|
|
2196
|
+
}, skill.name)) });
|
|
2197
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2198
|
+
groupsState !== null && groupsState.collections.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2199
|
+
className: panel_module_css_default.empty,
|
|
2200
|
+
children: tt("groups.noCollections")
|
|
2201
|
+
}) : null,
|
|
2202
|
+
groupsState?.collections.map((collection) => {
|
|
2203
|
+
const skills = filterBySource(sorted, sourceFilter, origins).filter((skill) => collection.skillNames.includes(skill.name));
|
|
2204
|
+
const disabledMembers = (catalog?.disabled ?? []).filter((record) => collection.skillNames.includes(record.name) && (normalized.length === 0 || record.name.toLocaleLowerCase().includes(normalized) || record.description.toLocaleLowerCase().includes(normalized)) && (sourceFilter === "all" || (origins[record.name] ?? "private") === sourceFilter));
|
|
2205
|
+
const collapsed = collapsedGroups.has("col:" + collection.name);
|
|
2206
|
+
const view = groupSwitchView(collection.skillNames, viewNames);
|
|
2207
|
+
const check = sourceCheck[collection.name];
|
|
2208
|
+
const hasWritable = collection.skillNames.some((name) => actionNames.has(name));
|
|
2209
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2210
|
+
className: panel_module_css_default.section,
|
|
2211
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2212
|
+
className: panel_module_css_default.groupHead,
|
|
2213
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2214
|
+
type: "button",
|
|
2215
|
+
className: panel_module_css_default.disclosure,
|
|
2216
|
+
"aria-expanded": !collapsed,
|
|
2217
|
+
onClick: () => {
|
|
2218
|
+
toggleGroupCollapse("col:" + collection.name);
|
|
2219
|
+
},
|
|
2220
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.chevron + (collapsed ? " " + panel_module_css_default.chevronCollapsed : "") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2221
|
+
className: panel_module_css_default.groupTitle,
|
|
2222
|
+
children: [
|
|
2223
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
2224
|
+
className: panel_module_css_default.sourceLink,
|
|
2225
|
+
href: "https://github.com/" + collection.name,
|
|
2226
|
+
target: "_blank",
|
|
2227
|
+
rel: "noreferrer",
|
|
2228
|
+
onClick: (event) => {
|
|
2229
|
+
event.stopPropagation();
|
|
2230
|
+
},
|
|
2231
|
+
children: collection.name
|
|
2232
|
+
}),
|
|
2233
|
+
" · " + collection.skillNames.length,
|
|
2234
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupSummary, {
|
|
2235
|
+
members: collection.skillNames,
|
|
2236
|
+
hub
|
|
2237
|
+
})
|
|
2238
|
+
]
|
|
2239
|
+
})]
|
|
2240
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2241
|
+
className: panel_module_css_default.groupOps,
|
|
2242
|
+
children: [
|
|
2243
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SourceStatusBadge, {
|
|
2244
|
+
check,
|
|
2245
|
+
checking: checkingSource === collection.name,
|
|
2246
|
+
onCheck: () => {
|
|
2247
|
+
checkSources(collection.name);
|
|
2248
|
+
}
|
|
2249
|
+
}),
|
|
2250
|
+
check !== void 0 && check.changed && check.updated.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2251
|
+
type: "button",
|
|
2252
|
+
className: panel_module_css_default.opBtn,
|
|
2253
|
+
disabled: syncingSource !== null,
|
|
2254
|
+
onClick: (event) => {
|
|
2255
|
+
event.stopPropagation();
|
|
2256
|
+
requestSync(collection.name, check.updated);
|
|
2257
|
+
},
|
|
2258
|
+
children: syncingSource === collection.name ? tt("source.syncing") : tt("source.sync")
|
|
2259
|
+
}) : null,
|
|
2260
|
+
check !== void 0 && check.deleted.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2261
|
+
type: "button",
|
|
2262
|
+
className: panel_module_css_default.opBtn + " " + panel_module_css_default.opDanger,
|
|
2263
|
+
onClick: (event) => {
|
|
2264
|
+
event.stopPropagation();
|
|
2265
|
+
requestDelete(collection.name, check.deleted);
|
|
2266
|
+
},
|
|
2267
|
+
children: tt("source.followDelete")
|
|
2268
|
+
}) : null,
|
|
2269
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2270
|
+
type: "button",
|
|
2271
|
+
role: "switch",
|
|
2272
|
+
"aria-checked": view.state !== "off",
|
|
2273
|
+
"aria-label": collection.name,
|
|
2274
|
+
className: panel_module_css_default.switch + (view.state === "on" ? " " + panel_module_css_default.switchOn : view.state === "mixed" ? " " + panel_module_css_default.switchMixed : ""),
|
|
2275
|
+
disabled: batchBusy || collection.skillNames.length === 0 || view.state !== "off" && !hasWritable,
|
|
2276
|
+
title: view.state !== "off" && !hasWritable ? tt("groups.noWritable") : void 0,
|
|
2277
|
+
onClick: (event) => {
|
|
2278
|
+
event.stopPropagation();
|
|
2279
|
+
toggleGroup("col:" + collection.name, collection.name, view.state);
|
|
2280
|
+
},
|
|
2281
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.switchThumb })
|
|
2282
|
+
})
|
|
2283
|
+
]
|
|
2284
|
+
})]
|
|
2285
|
+
}), !collapsed ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [skills.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillRow, {
|
|
2286
|
+
skill,
|
|
2287
|
+
hub
|
|
2288
|
+
}, skill.name)), disabledMembers.map((record) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DisabledRow, {
|
|
2289
|
+
record,
|
|
2290
|
+
busy: busyNames.has(record.name),
|
|
2291
|
+
onEnable: () => {
|
|
2292
|
+
enableDisabled(record);
|
|
2293
|
+
}
|
|
2294
|
+
}, record.name))] }) : null]
|
|
2295
|
+
}, "col:" + collection.name);
|
|
2296
|
+
}),
|
|
2297
|
+
(() => {
|
|
2298
|
+
const uncategorized = filterBySource(sorted, sourceFilter, origins).filter((skill) => origins[skill.name] === void 0);
|
|
2299
|
+
if (uncategorized.length === 0) return null;
|
|
2300
|
+
const collapsed = collapsedGroups.has("uncategorized-source");
|
|
2301
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2302
|
+
className: panel_module_css_default.section,
|
|
2303
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2304
|
+
className: panel_module_css_default.groupHead,
|
|
2305
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2306
|
+
type: "button",
|
|
2307
|
+
className: panel_module_css_default.disclosure,
|
|
2308
|
+
"aria-expanded": !collapsed,
|
|
2309
|
+
onClick: () => {
|
|
2310
|
+
toggleGroupCollapse("uncategorized-source");
|
|
2311
|
+
},
|
|
2312
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.chevron + (collapsed ? " " + panel_module_css_default.chevronCollapsed : "") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2313
|
+
className: panel_module_css_default.groupTitle,
|
|
2314
|
+
children: [
|
|
2315
|
+
tt("groups.personal"),
|
|
2316
|
+
" · ",
|
|
2317
|
+
uncategorized.length,
|
|
2318
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupSummary, {
|
|
2319
|
+
members: uncategorized.map((skill) => skill.name),
|
|
2320
|
+
hub
|
|
2321
|
+
})
|
|
2322
|
+
]
|
|
2323
|
+
})]
|
|
2324
|
+
})
|
|
2325
|
+
}), !collapsed ? uncategorized.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillRow, {
|
|
2326
|
+
skill,
|
|
2327
|
+
hub
|
|
2328
|
+
}, skill.name)) : null]
|
|
2329
|
+
});
|
|
2330
|
+
})()
|
|
2331
|
+
] });
|
|
2332
|
+
}
|
|
2333
|
+
//#endregion
|
|
2334
|
+
//#region src/client/panel/ScenesView.tsx
|
|
2335
|
+
function ScenesView(props) {
|
|
2336
|
+
const { hub } = props;
|
|
2337
|
+
const { catalog, groupsState, sorted, normalized, collapsedGroups, viewNames, actionNames, batchBusy, busyNames, newTagName, setNewTagName, tagBusy, createTag, toggleGroupCollapse, toggleGroup, setEditingTag, setEditName, setMembersDraft, setEditSearch, enableDisabled } = hub;
|
|
2338
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2339
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("form", {
|
|
2340
|
+
className: panel_module_css_default.form,
|
|
2341
|
+
onSubmit: (event) => {
|
|
2342
|
+
createTag(event);
|
|
2343
|
+
},
|
|
2344
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2345
|
+
className: panel_module_css_default.buttons,
|
|
2346
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2347
|
+
className: panel_module_css_default.input + " " + panel_module_css_default.grow,
|
|
2348
|
+
value: newTagName,
|
|
2349
|
+
onChange: (event) => {
|
|
2350
|
+
setNewTagName(event.target.value);
|
|
2351
|
+
},
|
|
2352
|
+
placeholder: tt("groups.namePlaceholder")
|
|
2353
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2354
|
+
type: "submit",
|
|
2355
|
+
className: panel_module_css_default.button + " " + panel_module_css_default.primary,
|
|
2356
|
+
disabled: tagBusy || newTagName.trim() === "",
|
|
2357
|
+
children: tt("groups.new")
|
|
2358
|
+
})]
|
|
2359
|
+
})
|
|
2360
|
+
}),
|
|
2361
|
+
groupsState !== null && groupsState.tags.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2362
|
+
className: panel_module_css_default.empty,
|
|
2363
|
+
children: tt("groups.empty")
|
|
2364
|
+
}) : null,
|
|
2365
|
+
groupsState?.tags.map((tag) => {
|
|
2366
|
+
const skills = sorted.filter((skill) => tag.skillNames.includes(skill.name));
|
|
2367
|
+
const disabledMembers = (catalog?.disabled ?? []).filter((record) => tag.skillNames.includes(record.name) && (normalized.length === 0 || record.name.toLocaleLowerCase().includes(normalized) || record.description.toLocaleLowerCase().includes(normalized)));
|
|
2368
|
+
const collapsed = collapsedGroups.has("tag:" + tag.id);
|
|
2369
|
+
const view = groupSwitchView(tag.skillNames, viewNames);
|
|
2370
|
+
const hasWritable = tag.skillNames.some((name) => actionNames.has(name));
|
|
2371
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2372
|
+
className: panel_module_css_default.section,
|
|
2373
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2374
|
+
className: panel_module_css_default.groupHead,
|
|
2375
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2376
|
+
type: "button",
|
|
2377
|
+
className: panel_module_css_default.disclosure,
|
|
2378
|
+
"aria-expanded": !collapsed,
|
|
2379
|
+
onClick: () => {
|
|
2380
|
+
toggleGroupCollapse("tag:" + tag.id);
|
|
2381
|
+
},
|
|
2382
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.chevron + (collapsed ? " " + panel_module_css_default.chevronCollapsed : "") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2383
|
+
className: panel_module_css_default.groupTitle,
|
|
2384
|
+
children: [
|
|
2385
|
+
tag.name,
|
|
2386
|
+
" · ",
|
|
2387
|
+
tag.skillNames.length,
|
|
2388
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupSummary, {
|
|
2389
|
+
members: tag.skillNames,
|
|
2390
|
+
hub
|
|
2391
|
+
})
|
|
2392
|
+
]
|
|
2393
|
+
})]
|
|
2394
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2395
|
+
className: panel_module_css_default.groupOps,
|
|
2396
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2397
|
+
type: "button",
|
|
2398
|
+
role: "switch",
|
|
2399
|
+
"aria-checked": view.state !== "off",
|
|
2400
|
+
"aria-label": tag.name,
|
|
2401
|
+
className: panel_module_css_default.switch + (view.state === "on" ? " " + panel_module_css_default.switchOn : view.state === "mixed" ? " " + panel_module_css_default.switchMixed : ""),
|
|
2402
|
+
disabled: batchBusy || tag.skillNames.length === 0 || view.state !== "off" && !hasWritable,
|
|
2403
|
+
title: view.state !== "off" && !hasWritable ? tt("groups.noWritable") : void 0,
|
|
2404
|
+
onClick: (event) => {
|
|
2405
|
+
event.stopPropagation();
|
|
2406
|
+
toggleGroup("tag:" + tag.id, tag.name, view.state);
|
|
2407
|
+
},
|
|
2408
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.switchThumb })
|
|
2409
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2410
|
+
type: "button",
|
|
2411
|
+
className: panel_module_css_default.opBtn,
|
|
2412
|
+
onClick: () => {
|
|
2413
|
+
setEditingTag(tag);
|
|
2414
|
+
setEditName(tag.name);
|
|
2415
|
+
setMembersDraft(new Set(tag.skillNames));
|
|
2416
|
+
setEditSearch("");
|
|
2417
|
+
},
|
|
2418
|
+
children: tt("groups.edit")
|
|
2419
|
+
})]
|
|
2420
|
+
})]
|
|
2421
|
+
}), !collapsed ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [skills.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillRow, {
|
|
2422
|
+
skill,
|
|
2423
|
+
hub
|
|
2424
|
+
}, skill.name)), disabledMembers.map((record) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DisabledRow, {
|
|
2425
|
+
record,
|
|
2426
|
+
busy: busyNames.has(record.name),
|
|
2427
|
+
onEnable: () => {
|
|
2428
|
+
enableDisabled(record);
|
|
2429
|
+
}
|
|
2430
|
+
}, record.name))] }) : null]
|
|
2431
|
+
}, "tag:" + tag.id);
|
|
2432
|
+
})
|
|
2433
|
+
] });
|
|
2434
|
+
}
|
|
2435
|
+
//#endregion
|
|
2436
|
+
//#region src/client/market-catalog.ts
|
|
2437
|
+
/** 默认市场目录:精选技能仓库(顺序即展示顺序)。 */
|
|
2438
|
+
const MARKET_CATALOG = [
|
|
2439
|
+
{
|
|
2440
|
+
repo: "anthropics/skills",
|
|
2441
|
+
descriptionKey: "market.catalog.anthropics"
|
|
2442
|
+
},
|
|
2443
|
+
{
|
|
2444
|
+
repo: "obra/superpowers",
|
|
2445
|
+
descriptionKey: "market.catalog.superpowers"
|
|
2446
|
+
},
|
|
2447
|
+
{
|
|
2448
|
+
repo: "mattpocock/skills",
|
|
2449
|
+
descriptionKey: "market.catalog.mattpocock"
|
|
2450
|
+
},
|
|
2451
|
+
{
|
|
2452
|
+
repo: "nexu-io/open-design",
|
|
2453
|
+
descriptionKey: "market.catalog.openDesign"
|
|
2454
|
+
}
|
|
2455
|
+
];
|
|
2456
|
+
//#endregion
|
|
2457
|
+
//#region src/client/panel/dialogs.tsx
|
|
2458
|
+
/**
|
|
2459
|
+
* Shared dialog family for the panel: one overlay shell plus the specific
|
|
2460
|
+
* dialogs (group-close conflict, sync/delete confirm, branch picker, and
|
|
2461
|
+
* market sync selection). Every dialog closes on outside click and keeps
|
|
2462
|
+
* the same role/aria shell; the panel owns all dialog state.
|
|
2463
|
+
*/
|
|
2464
|
+
/** Overlay + centered alert-dialog shell; outside click or Escape cancels. */
|
|
2465
|
+
function DialogShell(props) {
|
|
2466
|
+
(0, react.useEffect)(() => {
|
|
2467
|
+
const onKey = (event) => {
|
|
2468
|
+
if (event.key === "Escape") props.onClose();
|
|
2469
|
+
};
|
|
2470
|
+
window.addEventListener("keydown", onKey);
|
|
2471
|
+
return () => {
|
|
2472
|
+
window.removeEventListener("keydown", onKey);
|
|
2473
|
+
};
|
|
2474
|
+
});
|
|
2475
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2476
|
+
className: panel_module_css_default.dialogOverlay,
|
|
2477
|
+
onClick: props.onClose,
|
|
2478
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2479
|
+
className: panel_module_css_default.dialog,
|
|
2480
|
+
role: "alertdialog",
|
|
2481
|
+
"aria-modal": "true",
|
|
2482
|
+
onClick: (event) => {
|
|
2483
|
+
event.stopPropagation();
|
|
2484
|
+
},
|
|
2485
|
+
children: props.children
|
|
2486
|
+
})
|
|
2487
|
+
});
|
|
2488
|
+
}
|
|
2489
|
+
/** Simple confirm dialog: title + text + optional skill list + cancel/confirm. */
|
|
2490
|
+
function ConfirmDialog(props) {
|
|
2491
|
+
const { title, text, items, confirmLabel, danger, onCancel, onConfirm } = props;
|
|
2492
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogShell, {
|
|
2493
|
+
onClose: onCancel,
|
|
2494
|
+
children: [
|
|
2495
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
2496
|
+
className: panel_module_css_default.dialogTitle,
|
|
2497
|
+
children: title
|
|
2498
|
+
}),
|
|
2499
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2500
|
+
className: panel_module_css_default.dialogText,
|
|
2501
|
+
children: text
|
|
2502
|
+
}),
|
|
2503
|
+
items !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
2504
|
+
className: panel_module_css_default.dialogList,
|
|
2505
|
+
children: items.map((name) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2506
|
+
className: panel_module_css_default.rowNameText,
|
|
2507
|
+
children: name
|
|
2508
|
+
}) }, name))
|
|
2509
|
+
}) : null,
|
|
2510
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2511
|
+
className: panel_module_css_default.dialogActions,
|
|
2512
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2513
|
+
type: "button",
|
|
2514
|
+
className: panel_module_css_default.button,
|
|
2515
|
+
onClick: onCancel,
|
|
2516
|
+
children: tt("form.cancel")
|
|
2517
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2518
|
+
type: "button",
|
|
2519
|
+
className: panel_module_css_default.button + (danger === true ? " " + panel_module_css_default.danger : " " + panel_module_css_default.primary),
|
|
2520
|
+
onClick: onConfirm,
|
|
2521
|
+
children: confirmLabel
|
|
2522
|
+
})]
|
|
2523
|
+
})
|
|
2524
|
+
]
|
|
2525
|
+
});
|
|
2526
|
+
}
|
|
2527
|
+
/** Group-close conflict: which skills stay on, and which other groups also enable them. */
|
|
2528
|
+
function ConflictDialog(props) {
|
|
2529
|
+
const { dialog, tags, collections, onClose, onKeepOn, onCloseAll } = props;
|
|
2530
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogShell, {
|
|
2531
|
+
onClose,
|
|
2532
|
+
children: [
|
|
2533
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
2534
|
+
className: panel_module_css_default.dialogTitle,
|
|
2535
|
+
children: tt("groups.conflictTitle")
|
|
2536
|
+
}),
|
|
2537
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2538
|
+
className: panel_module_css_default.dialogText,
|
|
2539
|
+
children: tt("groups.conflictText")
|
|
2540
|
+
}),
|
|
2541
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
2542
|
+
className: panel_module_css_default.dialogList,
|
|
2543
|
+
children: dialog.conflicts.map((name) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2544
|
+
className: panel_module_css_default.rowNameText,
|
|
2545
|
+
children: name
|
|
2546
|
+
}), " — " + groupNamesOf(name, [...tags], [...collections]).join(", ")] }, name))
|
|
2547
|
+
}),
|
|
2548
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2549
|
+
className: panel_module_css_default.dialogActions,
|
|
2550
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2551
|
+
type: "button",
|
|
2552
|
+
className: panel_module_css_default.button,
|
|
2553
|
+
onClick: onKeepOn,
|
|
2554
|
+
children: tt("groups.keepOn")
|
|
2555
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2556
|
+
type: "button",
|
|
2557
|
+
className: panel_module_css_default.button + " " + panel_module_css_default.primary,
|
|
2558
|
+
onClick: onCloseAll,
|
|
2559
|
+
children: tt("groups.closeAll")
|
|
2560
|
+
})]
|
|
2561
|
+
})
|
|
2562
|
+
]
|
|
2563
|
+
});
|
|
2564
|
+
}
|
|
2565
|
+
/** Branch picker for a repo without releases (pins the ref, then rescans). */
|
|
2566
|
+
function BranchChoiceDialog(props) {
|
|
2567
|
+
const { choice, busy, onSelect, onCancel, onConfirm } = props;
|
|
2568
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogShell, {
|
|
2569
|
+
onClose: onCancel,
|
|
2570
|
+
children: [
|
|
2571
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
2572
|
+
className: panel_module_css_default.dialogTitle,
|
|
2573
|
+
children: tt("market.branchTitle")
|
|
2574
|
+
}),
|
|
2575
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2576
|
+
className: panel_module_css_default.dialogText,
|
|
2577
|
+
children: tt("market.branchHint")
|
|
2578
|
+
}),
|
|
2579
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
2580
|
+
className: panel_module_css_default.select + " " + panel_module_css_default.dialogSelect,
|
|
2581
|
+
value: choice.selected,
|
|
2582
|
+
onChange: (event) => {
|
|
2583
|
+
onSelect(event.target.value);
|
|
2584
|
+
},
|
|
2585
|
+
children: choice.branches.map((branch) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2586
|
+
value: branch,
|
|
2587
|
+
children: branch
|
|
2588
|
+
}, branch))
|
|
2589
|
+
}),
|
|
2590
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2591
|
+
className: panel_module_css_default.dialogActions,
|
|
2592
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2593
|
+
type: "button",
|
|
2594
|
+
className: panel_module_css_default.button,
|
|
2595
|
+
onClick: onCancel,
|
|
2596
|
+
children: tt("form.cancel")
|
|
2597
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2598
|
+
type: "button",
|
|
2599
|
+
className: panel_module_css_default.button + " " + panel_module_css_default.primary,
|
|
2600
|
+
disabled: busy,
|
|
2601
|
+
onClick: onConfirm,
|
|
2602
|
+
children: tt("market.branchConfirm")
|
|
2603
|
+
})]
|
|
2604
|
+
})
|
|
2605
|
+
]
|
|
2606
|
+
});
|
|
2607
|
+
}
|
|
2608
|
+
/** Post-sync selection: which tracked skills to batch-update to the new version. */
|
|
2609
|
+
function MarketSyncDialog(props) {
|
|
2610
|
+
const { dialog, busy, onToggle, onCancel, onConfirm } = props;
|
|
2611
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogShell, {
|
|
2612
|
+
onClose: onCancel,
|
|
2613
|
+
children: [
|
|
2614
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
2615
|
+
className: panel_module_css_default.dialogTitle,
|
|
2616
|
+
children: tt("market.syncTitle")
|
|
2617
|
+
}),
|
|
2618
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2619
|
+
className: panel_module_css_default.dialogText,
|
|
2620
|
+
children: tt("market.syncText", { ref: dialog.ref })
|
|
2621
|
+
}),
|
|
2622
|
+
dialog.skills.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2623
|
+
className: panel_module_css_default.dialogText,
|
|
2624
|
+
children: tt("market.syncNone")
|
|
2625
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2626
|
+
className: panel_module_css_default.dialogList,
|
|
2627
|
+
children: dialog.skills.map((name) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2628
|
+
className: panel_module_css_default.dialogRow,
|
|
2629
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2630
|
+
type: "checkbox",
|
|
2631
|
+
checked: dialog.selected.has(name),
|
|
2632
|
+
onChange: (event) => {
|
|
2633
|
+
onToggle(name, event.target.checked);
|
|
2634
|
+
}
|
|
2635
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2636
|
+
className: panel_module_css_default.rowNameText,
|
|
2637
|
+
children: name
|
|
2638
|
+
})]
|
|
2639
|
+
}, name))
|
|
2640
|
+
}),
|
|
2641
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2642
|
+
className: panel_module_css_default.dialogActions,
|
|
2643
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2644
|
+
type: "button",
|
|
2645
|
+
className: panel_module_css_default.button,
|
|
2646
|
+
onClick: onCancel,
|
|
2647
|
+
children: tt("market.syncCancel")
|
|
2648
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2649
|
+
type: "button",
|
|
2650
|
+
className: panel_module_css_default.button + " " + panel_module_css_default.primary,
|
|
2651
|
+
disabled: busy || dialog.skills.length === 0,
|
|
2652
|
+
onClick: onConfirm,
|
|
2653
|
+
children: busy ? tt("source.syncing") : tt("source.sync")
|
|
2654
|
+
})]
|
|
2655
|
+
})
|
|
2656
|
+
]
|
|
2657
|
+
});
|
|
2658
|
+
}
|
|
2659
|
+
//#endregion
|
|
2660
|
+
//#region src/client/panel/MarketView.tsx
|
|
2661
|
+
/** Compact byte size for repo preview rows. */
|
|
2662
|
+
function formatBytes(bytes) {
|
|
2663
|
+
if (bytes < 1024) return bytes + " B";
|
|
2664
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
|
|
2665
|
+
return (bytes / 1024 / 1024).toFixed(1) + " MB";
|
|
2666
|
+
}
|
|
2667
|
+
function MarketView(props) {
|
|
2668
|
+
const { hub } = props;
|
|
2669
|
+
const { marketState, marketCheck, sourceCheck, sourcesState, repoDiscoverState, scanningRepo, repoSelected, setRepoSelected, repoImporting, repoResult, newSourceName, setNewSourceName, syncingMarket, tagBusy, addSource, addMarketSource, removeMarketSource, scanRepo, checkMarket, checkSources, syncMarketSource, toggleRepoSelected, importRepo, updateAllDialog, setUpdateAllDialog, updateAll } = hub;
|
|
2670
|
+
/** 有可更新技能的来源(全部更新按钮与确认列表共用)。 */
|
|
2671
|
+
const updatableRepos = Object.entries(sourceCheck).filter(([, check]) => check.changed && check.updated.length > 0);
|
|
2672
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2673
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2674
|
+
className: panel_module_css_default.section,
|
|
2675
|
+
children: [
|
|
2676
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2677
|
+
className: panel_module_css_default.sectionTitle,
|
|
2678
|
+
children: tt("market.catalogTitle")
|
|
2679
|
+
}),
|
|
2680
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2681
|
+
className: panel_module_css_default.hintLine + " " + panel_module_css_default.hintPadded,
|
|
2682
|
+
children: tt("market.catalogHint")
|
|
2683
|
+
}),
|
|
2684
|
+
MARKET_CATALOG.map((entry) => {
|
|
2685
|
+
const added = marketState.repos.some((record) => record.repo === entry.repo);
|
|
2686
|
+
const busy = scanningRepo !== null || tagBusy;
|
|
2687
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2688
|
+
className: panel_module_css_default.row + " " + panel_module_css_default.rowStatic,
|
|
2689
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2690
|
+
className: panel_module_css_default.rowMain,
|
|
2691
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2692
|
+
className: panel_module_css_default.rowName,
|
|
2693
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
2694
|
+
className: panel_module_css_default.sourceLink,
|
|
2695
|
+
href: "https://github.com/" + entry.repo,
|
|
2696
|
+
target: "_blank",
|
|
2697
|
+
rel: "noreferrer",
|
|
2698
|
+
children: entry.repo
|
|
2699
|
+
})
|
|
2700
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2701
|
+
className: panel_module_css_default.rowDesc,
|
|
2702
|
+
children: tt(entry.descriptionKey)
|
|
2703
|
+
})]
|
|
2704
|
+
}), added ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2705
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeReadonly,
|
|
2706
|
+
children: tt("market.added")
|
|
2707
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2708
|
+
type: "button",
|
|
2709
|
+
className: panel_module_css_default.opBtn,
|
|
2710
|
+
disabled: busy,
|
|
2711
|
+
onClick: () => {
|
|
2712
|
+
addSource(entry.repo);
|
|
2713
|
+
},
|
|
2714
|
+
children: tt("market.add")
|
|
2715
|
+
})]
|
|
2716
|
+
}, entry.repo);
|
|
2717
|
+
})
|
|
2718
|
+
]
|
|
2719
|
+
}),
|
|
2720
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2721
|
+
className: panel_module_css_default.section,
|
|
2722
|
+
children: [
|
|
2723
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2724
|
+
className: panel_module_css_default.sectionTitle + " " + panel_module_css_default.sectionHeadRow,
|
|
2725
|
+
children: [
|
|
2726
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2727
|
+
className: panel_module_css_default.sectionTitleFill,
|
|
2728
|
+
children: tt("market.mySources")
|
|
2729
|
+
}),
|
|
2730
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2731
|
+
type: "button",
|
|
2732
|
+
className: panel_module_css_default.opBtn,
|
|
2733
|
+
onClick: () => {
|
|
2734
|
+
checkSources();
|
|
2735
|
+
checkMarket();
|
|
2736
|
+
},
|
|
2737
|
+
children: tt("market.checkAll")
|
|
2738
|
+
}),
|
|
2739
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2740
|
+
type: "button",
|
|
2741
|
+
className: panel_module_css_default.opBtn + (updatableRepos.length > 0 ? " " + panel_module_css_default.opDanger : ""),
|
|
2742
|
+
disabled: updatableRepos.length === 0 || tagBusy,
|
|
2743
|
+
onClick: () => {
|
|
2744
|
+
setUpdateAllDialog(true);
|
|
2745
|
+
},
|
|
2746
|
+
children: tt("market.updateAll")
|
|
2747
|
+
})
|
|
2748
|
+
]
|
|
2749
|
+
}),
|
|
2750
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2751
|
+
className: panel_module_css_default.hintLine + " " + panel_module_css_default.hintPadded,
|
|
2752
|
+
children: tt("market.addHint")
|
|
2753
|
+
}),
|
|
2754
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2755
|
+
className: panel_module_css_default.buttons + " " + panel_module_css_default.actionsPadded,
|
|
2756
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2757
|
+
className: panel_module_css_default.input + " " + panel_module_css_default.grow,
|
|
2758
|
+
value: newSourceName,
|
|
2759
|
+
onChange: (event) => {
|
|
2760
|
+
setNewSourceName(event.target.value);
|
|
2761
|
+
},
|
|
2762
|
+
onKeyDown: (event) => {
|
|
2763
|
+
if (event.key === "Enter") {
|
|
2764
|
+
event.preventDefault();
|
|
2765
|
+
addMarketSource();
|
|
2766
|
+
}
|
|
2767
|
+
},
|
|
2768
|
+
placeholder: tt("market.addPlaceholder")
|
|
2769
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2770
|
+
type: "button",
|
|
2771
|
+
className: panel_module_css_default.button + " " + panel_module_css_default.primary,
|
|
2772
|
+
disabled: newSourceName.trim() === "",
|
|
2773
|
+
onClick: () => {
|
|
2774
|
+
addMarketSource();
|
|
2775
|
+
},
|
|
2776
|
+
children: tt("market.addSource")
|
|
2777
|
+
})]
|
|
2778
|
+
}),
|
|
2779
|
+
marketState.status === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2780
|
+
className: panel_module_css_default.empty,
|
|
2781
|
+
children: tt("panel.loading")
|
|
2782
|
+
}) : null,
|
|
2783
|
+
marketState.status === "ready" && marketState.repos.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2784
|
+
className: panel_module_css_default.empty,
|
|
2785
|
+
children: tt("market.noSources")
|
|
2786
|
+
}) : null,
|
|
2787
|
+
marketState.repos.map((record) => {
|
|
2788
|
+
const releaseCheck = marketCheck[record.repo];
|
|
2789
|
+
const skillCheck = sourceCheck[record.repo];
|
|
2790
|
+
const installedCount = sourcesState?.sources.find((source) => source.repo === record.repo)?.skills.length ?? 0;
|
|
2791
|
+
const scanning = scanningRepo === record.repo;
|
|
2792
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2793
|
+
className: panel_module_css_default.row + " " + panel_module_css_default.rowStatic,
|
|
2794
|
+
children: [
|
|
2795
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2796
|
+
className: panel_module_css_default.rowMain,
|
|
2797
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2798
|
+
className: panel_module_css_default.rowName,
|
|
2799
|
+
children: [
|
|
2800
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
2801
|
+
className: panel_module_css_default.sourceLink,
|
|
2802
|
+
href: "https://github.com/" + record.repo,
|
|
2803
|
+
target: "_blank",
|
|
2804
|
+
rel: "noreferrer",
|
|
2805
|
+
children: record.repo
|
|
2806
|
+
}),
|
|
2807
|
+
record.ref !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2808
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeSource,
|
|
2809
|
+
children: record.ref
|
|
2810
|
+
}) : null,
|
|
2811
|
+
installedCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2812
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeCount,
|
|
2813
|
+
children: tt("market.installed", { count: installedCount })
|
|
2814
|
+
}) : null,
|
|
2815
|
+
skillCheck?.changed === true && skillCheck.updated.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2816
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.statusUpdated,
|
|
2817
|
+
children: tt("market.updatable", { count: skillCheck.updated.length })
|
|
2818
|
+
}) : null,
|
|
2819
|
+
skillCheck !== void 0 && skillCheck.deleted.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2820
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.statusError,
|
|
2821
|
+
children: tt("market.deletedUpstream", { count: skillCheck.deleted.length })
|
|
2822
|
+
}) : null,
|
|
2823
|
+
releaseCheck?.updateAvailable === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2824
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.statusUpdated,
|
|
2825
|
+
children: releaseCheck.latestTag !== void 0 ? tt("market.newRelease", { version: releaseCheck.latestTag }) : tt("market.updated")
|
|
2826
|
+
}) : null
|
|
2827
|
+
]
|
|
2828
|
+
})
|
|
2829
|
+
}),
|
|
2830
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2831
|
+
type: "button",
|
|
2832
|
+
className: panel_module_css_default.opBtn,
|
|
2833
|
+
disabled: scanning,
|
|
2834
|
+
onClick: () => {
|
|
2835
|
+
checkMarket();
|
|
2836
|
+
},
|
|
2837
|
+
children: tt("market.check")
|
|
2838
|
+
}),
|
|
2839
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2840
|
+
type: "button",
|
|
2841
|
+
className: panel_module_css_default.opBtn + (releaseCheck?.updateAvailable === true ? " " + panel_module_css_default.opDanger : ""),
|
|
2842
|
+
disabled: syncingMarket === record.repo,
|
|
2843
|
+
onClick: () => {
|
|
2844
|
+
syncMarketSource(record.repo);
|
|
2845
|
+
},
|
|
2846
|
+
children: syncingMarket === record.repo ? tt("market.syncing") : tt("market.sync")
|
|
2847
|
+
}),
|
|
2848
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2849
|
+
type: "button",
|
|
2850
|
+
className: panel_module_css_default.opBtn,
|
|
2851
|
+
disabled: scanning,
|
|
2852
|
+
onClick: () => {
|
|
2853
|
+
scanRepo(record.repo);
|
|
2854
|
+
},
|
|
2855
|
+
children: scanning ? tt("market.scanning") : tt("market.scan")
|
|
2856
|
+
}),
|
|
2857
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2858
|
+
type: "button",
|
|
2859
|
+
className: panel_module_css_default.opBtn,
|
|
2860
|
+
disabled: tagBusy,
|
|
2861
|
+
title: tt("market.removeHint"),
|
|
2862
|
+
onClick: () => {
|
|
2863
|
+
removeMarketSource(record.repo);
|
|
2864
|
+
},
|
|
2865
|
+
children: tt("market.deleteSource")
|
|
2866
|
+
})
|
|
2867
|
+
]
|
|
2868
|
+
}, record.repo);
|
|
2869
|
+
})
|
|
2870
|
+
]
|
|
2871
|
+
}),
|
|
2872
|
+
repoDiscoverState.status === "scanning" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2873
|
+
className: panel_module_css_default.empty,
|
|
2874
|
+
children: tt("market.scanning")
|
|
2875
|
+
}) : null,
|
|
2876
|
+
repoDiscoverState.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2877
|
+
className: panel_module_css_default.errorBanner,
|
|
2878
|
+
children: repoDiscoverState.message
|
|
2879
|
+
}) : null,
|
|
2880
|
+
repoDiscoverState.status === "ready" ? (() => {
|
|
2881
|
+
const entries = repoDiscoverState.data.entries;
|
|
2882
|
+
const selected = entries.filter((entry) => repoSelected.has(entry.path) && !entry.existing);
|
|
2883
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2884
|
+
className: panel_module_css_default.hintLine + " " + panel_module_css_default.hintInline,
|
|
2885
|
+
children: tt("repo.ready", { count: entries.length })
|
|
2886
|
+
}), entries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2887
|
+
className: panel_module_css_default.empty,
|
|
2888
|
+
children: tt("repo.empty")
|
|
2889
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2890
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2891
|
+
className: panel_module_css_default.buttons + " " + panel_module_css_default.actionsBottom,
|
|
2892
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2893
|
+
type: "button",
|
|
2894
|
+
className: panel_module_css_default.button,
|
|
2895
|
+
onClick: () => {
|
|
2896
|
+
setRepoSelected(new Set(entries.filter((entry) => !entry.existing).map((entry) => entry.path)));
|
|
2897
|
+
},
|
|
2898
|
+
children: tt("repo.selectAll")
|
|
2899
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2900
|
+
type: "button",
|
|
2901
|
+
className: panel_module_css_default.button,
|
|
2902
|
+
onClick: () => {
|
|
2903
|
+
setRepoSelected(/* @__PURE__ */ new Set());
|
|
2904
|
+
},
|
|
2905
|
+
children: tt("repo.clearAll")
|
|
2906
|
+
})]
|
|
2907
|
+
}),
|
|
2908
|
+
["skills", "design-templates"].map((root) => {
|
|
2909
|
+
const rootEntries = entries.filter((entry) => entry.root === root);
|
|
2910
|
+
if (rootEntries.length === 0) return null;
|
|
2911
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2912
|
+
className: panel_module_css_default.section,
|
|
2913
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2914
|
+
className: panel_module_css_default.sectionTitle,
|
|
2915
|
+
children: root === "skills" ? tt("repo.root.skills") : tt("repo.root.designTemplates")
|
|
2916
|
+
}), rootEntries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2917
|
+
className: panel_module_css_default.row + (entry.existing ? " " + panel_module_css_default.rowMuted : ""),
|
|
2918
|
+
children: [
|
|
2919
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2920
|
+
type: "checkbox",
|
|
2921
|
+
checked: repoSelected.has(entry.path),
|
|
2922
|
+
disabled: entry.existing,
|
|
2923
|
+
onChange: (event) => {
|
|
2924
|
+
toggleRepoSelected(entry.path, event.target.checked);
|
|
2925
|
+
}
|
|
2926
|
+
}),
|
|
2927
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2928
|
+
className: panel_module_css_default.rowMain,
|
|
2929
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2930
|
+
className: panel_module_css_default.rowName,
|
|
2931
|
+
children: entry.name
|
|
2932
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2933
|
+
className: panel_module_css_default.rowDesc,
|
|
2934
|
+
children: [
|
|
2935
|
+
entry.dir,
|
|
2936
|
+
" · ",
|
|
2937
|
+
tt("repo.files", {
|
|
2938
|
+
count: entry.fileCount,
|
|
2939
|
+
size: formatBytes(entry.totalBytes)
|
|
2940
|
+
})
|
|
2941
|
+
]
|
|
2942
|
+
})]
|
|
2943
|
+
}),
|
|
2944
|
+
entry.existing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2945
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeReadonly,
|
|
2946
|
+
children: tt("repo.existing")
|
|
2947
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2948
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeSource,
|
|
2949
|
+
children: entry.origin
|
|
2950
|
+
})
|
|
2951
|
+
]
|
|
2952
|
+
}, entry.path))]
|
|
2953
|
+
}, root);
|
|
2954
|
+
}),
|
|
2955
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2956
|
+
type: "button",
|
|
2957
|
+
className: panel_module_css_default.button + " " + panel_module_css_default.primary,
|
|
2958
|
+
disabled: selected.length === 0 || repoImporting,
|
|
2959
|
+
onClick: () => {
|
|
2960
|
+
importRepo();
|
|
2961
|
+
},
|
|
2962
|
+
children: repoImporting ? tt("repo.importing") : tt("repo.import", { count: selected.length })
|
|
2963
|
+
})
|
|
2964
|
+
] })] });
|
|
2965
|
+
})() : null,
|
|
2966
|
+
repoResult !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2967
|
+
className: panel_module_css_default.formSuccess + " " + panel_module_css_default.actionsTop,
|
|
2968
|
+
children: [
|
|
2969
|
+
tt("repo.imported", { count: repoResult.imported.length }),
|
|
2970
|
+
" · ",
|
|
2971
|
+
tt("repo.skippedExisting", { count: repoResult.skipped.length }),
|
|
2972
|
+
" · ",
|
|
2973
|
+
tt("repo.failed", { count: repoResult.failed.length })
|
|
2974
|
+
]
|
|
2975
|
+
}) : null,
|
|
2976
|
+
updateAllDialog ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmDialog, {
|
|
2977
|
+
title: tt("market.updateAllConfirmTitle"),
|
|
2978
|
+
text: tt("market.updateAllConfirmText"),
|
|
2979
|
+
items: updatableRepos.map(([repo, check]) => tt("market.updateAllItem", {
|
|
2980
|
+
repo,
|
|
2981
|
+
count: check.updated.length
|
|
2982
|
+
})),
|
|
2983
|
+
confirmLabel: tt("market.updateAll"),
|
|
2984
|
+
danger: true,
|
|
2985
|
+
onCancel: () => {
|
|
2986
|
+
setUpdateAllDialog(false);
|
|
2987
|
+
},
|
|
2988
|
+
onConfirm: () => {
|
|
2989
|
+
updateAll();
|
|
2990
|
+
}
|
|
2991
|
+
}) : null
|
|
2992
|
+
] });
|
|
2993
|
+
}
|
|
2994
|
+
//#endregion
|
|
2995
|
+
//#region src/client/panel/useSkillHub.ts
|
|
2996
|
+
/**
|
|
2997
|
+
* useSkillHub — the panel's state owner. Everything the skill hub panel
|
|
2998
|
+
* knows (catalog, groups, sources, market, dialogs, drafts) plus every
|
|
2999
|
+
* async flow lives here; the panel itself only renders. All hooks run
|
|
3000
|
+
* unconditionally at the top, so the panel may early-return for the detail
|
|
3001
|
+
* and tag-editor views without violating the rules of hooks.
|
|
3002
|
+
*/
|
|
3003
|
+
/** Catalog poll interval while the panel is mounted (the provider watcher feeds this). */
|
|
3004
|
+
const POLL_MS = 5e3;
|
|
3005
|
+
/** Relaxed poll for slower-changing data (stats, groups, sources, config). */
|
|
3006
|
+
const SLOW_POLL_MS = 6e4;
|
|
3007
|
+
/** localStorage key of the last daily auto-check timestamp (survives sessions). */
|
|
3008
|
+
const AUTO_CHECK_KEY = "skill-hub.last-auto-check";
|
|
3009
|
+
/** Daily auto-check interval: at most one network round per day. */
|
|
3010
|
+
const AUTO_CHECK_INTERVAL_MS = 1440 * 6e4;
|
|
3011
|
+
/** Whether the daily auto-check should run now (first visit or ≥24h since the last). */
|
|
3012
|
+
function shouldAutoCheck() {
|
|
3013
|
+
try {
|
|
3014
|
+
const raw = localStorage.getItem(AUTO_CHECK_KEY);
|
|
3015
|
+
const last = raw === null ? NaN : Number(raw);
|
|
3016
|
+
if (!Number.isFinite(last)) return true;
|
|
3017
|
+
return Date.now() - last >= AUTO_CHECK_INTERVAL_MS;
|
|
3018
|
+
} catch {
|
|
3019
|
+
return true;
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
/** Record that the daily auto-check ran. */
|
|
3023
|
+
function markAutoChecked() {
|
|
3024
|
+
try {
|
|
3025
|
+
localStorage.setItem(AUTO_CHECK_KEY, String(Date.now()));
|
|
3026
|
+
} catch {}
|
|
3027
|
+
}
|
|
3028
|
+
function useSkillHub(api) {
|
|
3029
|
+
const [catalog, setCatalog] = (0, react.useState)(null);
|
|
3030
|
+
const [loading, setLoading] = (0, react.useState)(true);
|
|
3031
|
+
const [loadError, setLoadError] = (0, react.useState)(null);
|
|
3032
|
+
/** Green success banner (create finished); shown outside the closing form. */
|
|
3033
|
+
const [successBanner, setSuccessBanner] = (0, react.useState)(null);
|
|
3034
|
+
const [updateState, setUpdateState] = (0, react.useState)({ status: "idle" });
|
|
3035
|
+
const [repoDiscoverState, setRepoDiscoverState] = (0, react.useState)({ status: "idle" });
|
|
3036
|
+
/** Market repo currently being scanned (null when idle); per-row busy state. */
|
|
3037
|
+
const [scanningRepo, setScanningRepo] = (0, react.useState)(null);
|
|
3038
|
+
const [repoSelected, setRepoSelected] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
3039
|
+
const [repoImporting, setRepoImporting] = (0, react.useState)(false);
|
|
3040
|
+
const [repoResult, setRepoResult] = (0, react.useState)(null);
|
|
3041
|
+
const [search, setSearch] = (0, react.useState)("");
|
|
3042
|
+
const [detail, setDetail] = (0, react.useState)(null);
|
|
3043
|
+
const [detailLoading, setDetailLoading] = (0, react.useState)(false);
|
|
3044
|
+
const [busyNames, setBusyNames] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
3045
|
+
const [batchBusy, setBatchBusy] = (0, react.useState)(false);
|
|
3046
|
+
const [showForm, setShowForm] = (0, react.useState)(false);
|
|
3047
|
+
const [formName, setFormName] = (0, react.useState)("");
|
|
3048
|
+
const [formDesc, setFormDesc] = (0, react.useState)("");
|
|
3049
|
+
const [formRoot, setFormRoot] = (0, react.useState)("user-dsh");
|
|
3050
|
+
const [formBusy, setFormBusy] = (0, react.useState)(false);
|
|
3051
|
+
const [formMessage, setFormMessage] = (0, react.useState)(null);
|
|
3052
|
+
const [uses, setUses] = (0, react.useState)(/* @__PURE__ */ new Map());
|
|
3053
|
+
const [hubConfig, setHubConfig] = (0, react.useState)(null);
|
|
3054
|
+
const [tab, setTab] = (0, react.useState)("sources");
|
|
3055
|
+
const [skillView, setSkillView] = (0, react.useState)("groups");
|
|
3056
|
+
const [sourceFilter, setSourceFilter] = (0, react.useState)("all");
|
|
3057
|
+
const [sortKey, setSortKey] = (0, react.useState)("name");
|
|
3058
|
+
const [marketState, setMarketState] = (0, react.useState)({
|
|
3059
|
+
status: "loading",
|
|
3060
|
+
repos: []
|
|
3061
|
+
});
|
|
3062
|
+
const [marketCheck, setMarketCheck] = (0, react.useState)({});
|
|
3063
|
+
const [branchChoice, setBranchChoice] = (0, react.useState)(null);
|
|
3064
|
+
const [branchBusy, setBranchBusy] = (0, react.useState)(false);
|
|
3065
|
+
const [marketSyncDialog, setMarketSyncDialog] = (0, react.useState)(null);
|
|
3066
|
+
const [syncingMarket, setSyncingMarket] = (0, react.useState)(null);
|
|
3067
|
+
const [syncBusy, setSyncBusy] = (0, react.useState)(false);
|
|
3068
|
+
const [newSourceName, setNewSourceName] = (0, react.useState)("");
|
|
3069
|
+
const [groupsState, setGroupsState] = (0, react.useState)(null);
|
|
3070
|
+
const [sourcesState, setSourcesState] = (0, react.useState)(null);
|
|
3071
|
+
const [sourceCheck, setSourceCheck] = (0, react.useState)({});
|
|
3072
|
+
const [checkingSource, setCheckingSource] = (0, react.useState)(null);
|
|
3073
|
+
const [syncingSource, setSyncingSource] = (0, react.useState)(null);
|
|
3074
|
+
const [conflictDialog, setConflictDialog] = (0, react.useState)(null);
|
|
3075
|
+
const [confirmDialog, setConfirmDialog] = (0, react.useState)(null);
|
|
3076
|
+
const [deleteSkillDialog, setDeleteSkillDialog] = (0, react.useState)(null);
|
|
3077
|
+
const [confirmClearTrash, setConfirmClearTrash] = (0, react.useState)(false);
|
|
3078
|
+
/** 「全部更新」确认对话框(市场 tab)。 */
|
|
3079
|
+
const [updateAllDialog, setUpdateAllDialog] = (0, react.useState)(false);
|
|
3080
|
+
const [editingTag, setEditingTag] = (0, react.useState)(null);
|
|
3081
|
+
const [editName, setEditName] = (0, react.useState)("");
|
|
3082
|
+
const [membersDraft, setMembersDraft] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
3083
|
+
const [newTagName, setNewTagName] = (0, react.useState)("");
|
|
3084
|
+
const [tagBusy, setTagBusy] = (0, react.useState)(false);
|
|
3085
|
+
const [editSearch, setEditSearch] = (0, react.useState)("");
|
|
3086
|
+
/** 分组视图里收起的分组(key 为 tag:<id> 或 col:<name>)。 */
|
|
3087
|
+
const [collapsedGroups, setCollapsedGroups] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
3088
|
+
const [showLegend, setShowLegend] = (0, react.useState)(false);
|
|
3089
|
+
const toggleGroupCollapse = (0, react.useCallback)((key) => {
|
|
3090
|
+
setCollapsedGroups((previous) => {
|
|
3091
|
+
const next = new Set(previous);
|
|
3092
|
+
if (next.has(key)) next.delete(key);
|
|
3093
|
+
else next.add(key);
|
|
3094
|
+
return next;
|
|
3095
|
+
});
|
|
3096
|
+
}, []);
|
|
3097
|
+
const load = (0, react.useCallback)(async () => {
|
|
944
3098
|
try {
|
|
945
3099
|
const next = await api.catalog();
|
|
946
3100
|
setCatalog(next);
|
|
@@ -954,20 +3108,102 @@ window.__ModuleLoader__.load({
|
|
|
954
3108
|
const loadUses = (0, react.useCallback)(async () => {
|
|
955
3109
|
try {
|
|
956
3110
|
const result = await api.stats();
|
|
957
|
-
if (result.available) setUses(new Map(result.stats.map((stat) => [stat.name,
|
|
3111
|
+
if (result.available) setUses(new Map(result.stats.map((stat) => [stat.name, {
|
|
3112
|
+
count: stat.count,
|
|
3113
|
+
...stat.lastUsed !== void 0 ? { lastUsed: stat.lastUsed } : {}
|
|
3114
|
+
}])));
|
|
3115
|
+
} catch {}
|
|
3116
|
+
}, [api]);
|
|
3117
|
+
const loadConfig = (0, react.useCallback)(async () => {
|
|
3118
|
+
try {
|
|
3119
|
+
setHubConfig((await api.config()).config);
|
|
958
3120
|
} catch {}
|
|
959
3121
|
}, [api]);
|
|
3122
|
+
/** Check the hub's own GitHub release once, and again on demand. */
|
|
3123
|
+
const checkUpdate = (0, react.useCallback)(async () => {
|
|
3124
|
+
setUpdateState({ status: "checking" });
|
|
3125
|
+
try {
|
|
3126
|
+
setUpdateState({
|
|
3127
|
+
status: "ready",
|
|
3128
|
+
data: await api.updateCheck()
|
|
3129
|
+
});
|
|
3130
|
+
} catch (error) {
|
|
3131
|
+
setUpdateState({
|
|
3132
|
+
status: "error",
|
|
3133
|
+
message: errorMessage(error)
|
|
3134
|
+
});
|
|
3135
|
+
}
|
|
3136
|
+
}, [api]);
|
|
3137
|
+
/** 加载用户 tag 分组 + 系统集合组 + origin 映射。 */
|
|
3138
|
+
const loadGroups = (0, react.useCallback)(async () => {
|
|
3139
|
+
try {
|
|
3140
|
+
setGroupsState(await api.groups());
|
|
3141
|
+
} catch (error) {
|
|
3142
|
+
setLoadError(errorMessage(error));
|
|
3143
|
+
}
|
|
3144
|
+
}, [api]);
|
|
3145
|
+
/** 加载来源记录 + 回收站。 */
|
|
3146
|
+
const loadSources = (0, react.useCallback)(async () => {
|
|
3147
|
+
try {
|
|
3148
|
+
setSourcesState(await api.sources());
|
|
3149
|
+
} catch (error) {
|
|
3150
|
+
setLoadError(errorMessage(error));
|
|
3151
|
+
}
|
|
3152
|
+
}, [api]);
|
|
3153
|
+
/** 加载市场源列表。 */
|
|
3154
|
+
const loadMarket = (0, react.useCallback)(async () => {
|
|
3155
|
+
try {
|
|
3156
|
+
const next = await api.market();
|
|
3157
|
+
setMarketState({
|
|
3158
|
+
status: "ready",
|
|
3159
|
+
repos: next.repos
|
|
3160
|
+
});
|
|
3161
|
+
} catch (error) {
|
|
3162
|
+
setMarketState({
|
|
3163
|
+
status: "error",
|
|
3164
|
+
repos: []
|
|
3165
|
+
});
|
|
3166
|
+
setLoadError(errorMessage(error));
|
|
3167
|
+
}
|
|
3168
|
+
}, [api]);
|
|
3169
|
+
/** 把最新的 tag 列表合并进 groups 状态(tags 以外的字段保持原样)。 */
|
|
3170
|
+
const applyTags = (0, react.useCallback)((tags) => {
|
|
3171
|
+
setGroupsState((previous) => previous === null ? {
|
|
3172
|
+
ok: true,
|
|
3173
|
+
tags,
|
|
3174
|
+
collections: [],
|
|
3175
|
+
origins: {}
|
|
3176
|
+
} : {
|
|
3177
|
+
...previous,
|
|
3178
|
+
tags
|
|
3179
|
+
});
|
|
3180
|
+
}, []);
|
|
960
3181
|
(0, react.useEffect)(() => {
|
|
961
3182
|
load();
|
|
962
3183
|
loadUses();
|
|
963
|
-
|
|
3184
|
+
loadGroups();
|
|
3185
|
+
loadSources();
|
|
3186
|
+
loadConfig();
|
|
3187
|
+
const fast = window.setInterval(() => {
|
|
964
3188
|
load();
|
|
965
|
-
loadUses();
|
|
966
3189
|
}, POLL_MS);
|
|
3190
|
+
const slow = window.setInterval(() => {
|
|
3191
|
+
loadUses();
|
|
3192
|
+
loadGroups();
|
|
3193
|
+
loadSources();
|
|
3194
|
+
loadConfig();
|
|
3195
|
+
}, SLOW_POLL_MS);
|
|
967
3196
|
return () => {
|
|
968
|
-
window.clearInterval(
|
|
3197
|
+
window.clearInterval(fast);
|
|
3198
|
+
window.clearInterval(slow);
|
|
969
3199
|
};
|
|
970
|
-
}, [
|
|
3200
|
+
}, [
|
|
3201
|
+
load,
|
|
3202
|
+
loadUses,
|
|
3203
|
+
loadGroups,
|
|
3204
|
+
loadSources,
|
|
3205
|
+
loadConfig
|
|
3206
|
+
]);
|
|
971
3207
|
const openDetail = (0, react.useCallback)(async (name) => {
|
|
972
3208
|
setDetailLoading(true);
|
|
973
3209
|
setLoadError(null);
|
|
@@ -994,37 +3230,552 @@ window.__ModuleLoader__.load({
|
|
|
994
3230
|
return next;
|
|
995
3231
|
});
|
|
996
3232
|
}
|
|
997
|
-
}, [api]);
|
|
998
|
-
const enableDisabled = (0, react.useCallback)(async (record) => {
|
|
999
|
-
setBusyNames((previous) => new Set(previous).add(record.name));
|
|
3233
|
+
}, [api]);
|
|
3234
|
+
const enableDisabled = (0, react.useCallback)(async (record) => {
|
|
3235
|
+
setBusyNames((previous) => new Set(previous).add(record.name));
|
|
3236
|
+
setLoadError(null);
|
|
3237
|
+
try {
|
|
3238
|
+
const next = await api.toggle(record.name, true);
|
|
3239
|
+
setCatalog(next);
|
|
3240
|
+
} catch (error) {
|
|
3241
|
+
setLoadError(errorMessage(error));
|
|
3242
|
+
} finally {
|
|
3243
|
+
setBusyNames((previous) => {
|
|
3244
|
+
const next = new Set(previous);
|
|
3245
|
+
next.delete(record.name);
|
|
3246
|
+
return next;
|
|
3247
|
+
});
|
|
3248
|
+
}
|
|
3249
|
+
}, [api]);
|
|
3250
|
+
/** Toggle an explicit name set in one write (enables disabled members too). */
|
|
3251
|
+
const batchToggleNames = (0, react.useCallback)(async (names, enabled) => {
|
|
3252
|
+
if (names.length === 0) return;
|
|
3253
|
+
setBatchBusy(true);
|
|
3254
|
+
setLoadError(null);
|
|
3255
|
+
try {
|
|
3256
|
+
const next = await api.toggleBatch(names, enabled);
|
|
3257
|
+
setCatalog(next.catalog);
|
|
3258
|
+
if (next.failures.length > 0) setLoadError("toggle-batch: " + next.failures.map((failure) => failure.name + ": " + failure.error).join("; "));
|
|
3259
|
+
} catch (error) {
|
|
3260
|
+
setLoadError(errorMessage(error));
|
|
3261
|
+
} finally {
|
|
3262
|
+
setBatchBusy(false);
|
|
3263
|
+
}
|
|
3264
|
+
}, [api]);
|
|
3265
|
+
/** 所有分组:key → 成员名(tag 与来源集合)。 */
|
|
3266
|
+
const groupMap = (0, react.useCallback)(() => {
|
|
3267
|
+
const map = /* @__PURE__ */ new Map();
|
|
3268
|
+
for (const tag of groupsState?.tags ?? []) map.set("tag:" + tag.id, tag.skillNames);
|
|
3269
|
+
for (const collection of groupsState?.collections ?? []) map.set("col:" + collection.name, collection.skillNames);
|
|
3270
|
+
return map;
|
|
3271
|
+
}, [groupsState]);
|
|
3272
|
+
/** 当前启用且可写的技能名(组开关只作用于它们)。 */
|
|
3273
|
+
const actionNames = (0, react.useMemo)(() => new Set((catalog?.skills ?? []).filter((skill) => skill.writable).map((skill) => skill.name)), [catalog]);
|
|
3274
|
+
/** 当前启用的全部技能名(含只读,用于派生开关状态)。 */
|
|
3275
|
+
const viewNames = (0, react.useMemo)(() => new Set((catalog?.skills ?? []).map((skill) => skill.name)), [catalog]);
|
|
3276
|
+
/**
|
|
3277
|
+
* Toggle a whole group via its tri-state switch. Closing with members that
|
|
3278
|
+
* are enabled in other groups opens the conflict dialog instead.
|
|
3279
|
+
*/
|
|
3280
|
+
const toggleGroup = (0, react.useCallback)((key, name, view) => {
|
|
3281
|
+
const members = groupMap().get(key) ?? [];
|
|
3282
|
+
if (members.length === 0) return;
|
|
3283
|
+
if (view === "off") {
|
|
3284
|
+
batchToggleNames(members, true);
|
|
3285
|
+
return;
|
|
3286
|
+
}
|
|
3287
|
+
const others = [...groupMap().entries()].filter(([otherKey]) => otherKey !== key).map(([, memberNames]) => ({ members: memberNames }));
|
|
3288
|
+
const conflicts = conflictsOnClose(members, actionNames, others);
|
|
3289
|
+
if (conflicts.length > 0) setConflictDialog({
|
|
3290
|
+
key,
|
|
3291
|
+
name,
|
|
3292
|
+
conflicts
|
|
3293
|
+
});
|
|
3294
|
+
else batchToggleNames(members.filter((member) => actionNames.has(member)), false);
|
|
3295
|
+
}, [
|
|
3296
|
+
groupMap,
|
|
3297
|
+
actionNames,
|
|
3298
|
+
batchToggleNames
|
|
3299
|
+
]);
|
|
3300
|
+
/** Resolve the open conflict dialog. */
|
|
3301
|
+
const resolveConflict = (0, react.useCallback)(async (closeAll) => {
|
|
3302
|
+
const dialog = conflictDialog;
|
|
3303
|
+
if (dialog === null) return;
|
|
3304
|
+
setConflictDialog(null);
|
|
3305
|
+
const members = groupMap().get(dialog.key) ?? [];
|
|
3306
|
+
if (closeAll) await batchToggleNames(members.filter((member) => actionNames.has(member)), false);
|
|
3307
|
+
else await batchToggleNames(members.filter((member) => actionNames.has(member) && !dialog.conflicts.includes(member)), false);
|
|
3308
|
+
}, [
|
|
3309
|
+
conflictDialog,
|
|
3310
|
+
groupMap,
|
|
3311
|
+
actionNames,
|
|
3312
|
+
batchToggleNames
|
|
3313
|
+
]);
|
|
3314
|
+
/** Dismiss a confirm dialog and run the pending source action. */
|
|
3315
|
+
const runConfirmed = (0, react.useCallback)(async () => {
|
|
3316
|
+
const dialog = confirmDialog;
|
|
3317
|
+
if (dialog === null) return;
|
|
3318
|
+
setConfirmDialog(null);
|
|
3319
|
+
if (dialog.kind === "sync") {
|
|
3320
|
+
setSyncingSource(dialog.repo);
|
|
3321
|
+
setLoadError(null);
|
|
3322
|
+
try {
|
|
3323
|
+
const result = await api.syncSource(dialog.repo, dialog.skills);
|
|
3324
|
+
await Promise.all([
|
|
3325
|
+
load(),
|
|
3326
|
+
loadGroups(),
|
|
3327
|
+
loadSources()
|
|
3328
|
+
]);
|
|
3329
|
+
if (result.failed.length > 0) setLoadError("sync: " + result.failed.map((failure) => failure.name + ": " + failure.error).join("; "));
|
|
3330
|
+
} catch (error) {
|
|
3331
|
+
setLoadError(errorMessage(error));
|
|
3332
|
+
} finally {
|
|
3333
|
+
setSyncingSource(null);
|
|
3334
|
+
}
|
|
3335
|
+
} else {
|
|
3336
|
+
setLoadError(null);
|
|
3337
|
+
try {
|
|
3338
|
+
await api.confirmDeleteSource(dialog.repo, dialog.skills);
|
|
3339
|
+
await Promise.all([
|
|
3340
|
+
load(),
|
|
3341
|
+
loadGroups(),
|
|
3342
|
+
loadSources()
|
|
3343
|
+
]);
|
|
3344
|
+
} catch (error) {
|
|
3345
|
+
setLoadError(errorMessage(error));
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
}, [
|
|
3349
|
+
confirmDialog,
|
|
3350
|
+
api,
|
|
3351
|
+
load,
|
|
3352
|
+
loadGroups,
|
|
3353
|
+
loadSources
|
|
3354
|
+
]);
|
|
3355
|
+
/** 检查全部来源的上游更新(服务端 5 分钟节流)。 */
|
|
3356
|
+
const checkSources = (0, react.useCallback)(async (repo) => {
|
|
3357
|
+
setCheckingSource(repo ?? "all");
|
|
3358
|
+
setLoadError(null);
|
|
3359
|
+
try {
|
|
3360
|
+
const result = await api.checkSources(repo);
|
|
3361
|
+
const next = { ...sourceCheck };
|
|
3362
|
+
for (const item of result.results) next[item.repo] = item;
|
|
3363
|
+
setSourceCheck(next);
|
|
3364
|
+
} catch (error) {
|
|
3365
|
+
setLoadError(errorMessage(error));
|
|
3366
|
+
} finally {
|
|
3367
|
+
setCheckingSource(null);
|
|
3368
|
+
}
|
|
3369
|
+
}, [api, sourceCheck]);
|
|
3370
|
+
/** 请求同步某个来源的所选技能(弹确认,因为会覆盖本地修改)。 */
|
|
3371
|
+
const requestSync = (0, react.useCallback)((repo, skills) => {
|
|
3372
|
+
setConfirmDialog({
|
|
3373
|
+
kind: "sync",
|
|
3374
|
+
repo,
|
|
3375
|
+
skills
|
|
3376
|
+
});
|
|
3377
|
+
}, []);
|
|
3378
|
+
/** 请求跟进上游删除(弹确认,移入回收站)。 */
|
|
3379
|
+
const requestDelete = (0, react.useCallback)((repo, skills) => {
|
|
3380
|
+
setConfirmDialog({
|
|
3381
|
+
kind: "delete",
|
|
3382
|
+
repo,
|
|
3383
|
+
skills
|
|
3384
|
+
});
|
|
3385
|
+
}, []);
|
|
3386
|
+
/** 从回收站恢复一个技能。 */
|
|
3387
|
+
const restoreTrash = (0, react.useCallback)(async (name) => {
|
|
3388
|
+
setTagBusy(true);
|
|
3389
|
+
setLoadError(null);
|
|
3390
|
+
try {
|
|
3391
|
+
await api.restoreSource(name);
|
|
3392
|
+
await Promise.all([
|
|
3393
|
+
load(),
|
|
3394
|
+
loadGroups(),
|
|
3395
|
+
loadSources()
|
|
3396
|
+
]);
|
|
3397
|
+
} catch (error) {
|
|
3398
|
+
setLoadError(errorMessage(error));
|
|
3399
|
+
} finally {
|
|
3400
|
+
setTagBusy(false);
|
|
3401
|
+
}
|
|
3402
|
+
}, [
|
|
3403
|
+
api,
|
|
3404
|
+
load,
|
|
3405
|
+
loadGroups,
|
|
3406
|
+
loadSources
|
|
3407
|
+
]);
|
|
3408
|
+
/** 确认后永久删除回收站里的全部技能。 */
|
|
3409
|
+
const clearTrash = (0, react.useCallback)(async () => {
|
|
3410
|
+
setConfirmClearTrash(false);
|
|
3411
|
+
setTagBusy(true);
|
|
3412
|
+
setLoadError(null);
|
|
3413
|
+
try {
|
|
3414
|
+
const result = await api.clearTrash();
|
|
3415
|
+
if (result.failed.length > 0) setLoadError("clear trash: " + result.failed.map((failure) => failure.name + ": " + failure.error).join("; "));
|
|
3416
|
+
await Promise.all([load(), loadSources()]);
|
|
3417
|
+
} catch (error) {
|
|
3418
|
+
setLoadError(errorMessage(error));
|
|
3419
|
+
} finally {
|
|
3420
|
+
setTagBusy(false);
|
|
3421
|
+
}
|
|
3422
|
+
}, [
|
|
3423
|
+
api,
|
|
3424
|
+
load,
|
|
3425
|
+
loadSources
|
|
3426
|
+
]);
|
|
3427
|
+
/** 打开单个技能的删除确认(移入回收站,可恢复)。 */
|
|
3428
|
+
const requestDeleteSkill = (0, react.useCallback)((name) => {
|
|
3429
|
+
setDeleteSkillDialog(name);
|
|
3430
|
+
}, []);
|
|
3431
|
+
/** 执行删除确认后的回收站迁移。 */
|
|
3432
|
+
const runDeleteSkill = (0, react.useCallback)(async () => {
|
|
3433
|
+
const name = deleteSkillDialog;
|
|
3434
|
+
if (name === null) return;
|
|
3435
|
+
setDeleteSkillDialog(null);
|
|
3436
|
+
setTagBusy(true);
|
|
3437
|
+
setLoadError(null);
|
|
3438
|
+
try {
|
|
3439
|
+
await api.deleteSkill(name);
|
|
3440
|
+
await Promise.all([
|
|
3441
|
+
load(),
|
|
3442
|
+
loadGroups(),
|
|
3443
|
+
loadSources()
|
|
3444
|
+
]);
|
|
3445
|
+
} catch (error) {
|
|
3446
|
+
setLoadError(errorMessage(error));
|
|
3447
|
+
} finally {
|
|
3448
|
+
setTagBusy(false);
|
|
3449
|
+
}
|
|
3450
|
+
}, [
|
|
3451
|
+
api,
|
|
3452
|
+
deleteSkillDialog,
|
|
3453
|
+
load,
|
|
3454
|
+
loadGroups,
|
|
3455
|
+
loadSources
|
|
3456
|
+
]);
|
|
3457
|
+
/** 新建一个空 tag 分组。 */
|
|
3458
|
+
const createTag = (0, react.useCallback)(async (event) => {
|
|
3459
|
+
event.preventDefault();
|
|
3460
|
+
const name = newTagName.trim();
|
|
3461
|
+
if (name === "") return;
|
|
3462
|
+
setTagBusy(true);
|
|
3463
|
+
setLoadError(null);
|
|
3464
|
+
try {
|
|
3465
|
+
applyTags(await api.saveTag({ name }));
|
|
3466
|
+
setNewTagName("");
|
|
3467
|
+
} catch (error) {
|
|
3468
|
+
setLoadError(errorMessage(error));
|
|
3469
|
+
} finally {
|
|
3470
|
+
setTagBusy(false);
|
|
3471
|
+
}
|
|
3472
|
+
}, [
|
|
3473
|
+
api,
|
|
3474
|
+
applyTags,
|
|
3475
|
+
newTagName
|
|
3476
|
+
]);
|
|
3477
|
+
/** 删除一个 tag 分组(不影响技能文件)。 */
|
|
3478
|
+
const deleteTag = (0, react.useCallback)(async (id) => {
|
|
3479
|
+
setTagBusy(true);
|
|
3480
|
+
setLoadError(null);
|
|
3481
|
+
try {
|
|
3482
|
+
applyTags(await api.deleteTag(id));
|
|
3483
|
+
setEditingTag(null);
|
|
3484
|
+
} catch (error) {
|
|
3485
|
+
setLoadError(errorMessage(error));
|
|
3486
|
+
} finally {
|
|
3487
|
+
setTagBusy(false);
|
|
3488
|
+
}
|
|
3489
|
+
}, [api, applyTags]);
|
|
3490
|
+
/** 重命名 tag,或保存成员勾选后回到列表。 */
|
|
3491
|
+
const saveTag = (0, react.useCallback)(async (id, name, memberNames) => {
|
|
3492
|
+
setTagBusy(true);
|
|
3493
|
+
setLoadError(null);
|
|
3494
|
+
try {
|
|
3495
|
+
const safeName = name.trim();
|
|
3496
|
+
let tags = null;
|
|
3497
|
+
if (safeName !== "") tags = await api.saveTag({
|
|
3498
|
+
id,
|
|
3499
|
+
name: safeName
|
|
3500
|
+
});
|
|
3501
|
+
if (memberNames !== null) tags = await api.setTagMembers(id, memberNames);
|
|
3502
|
+
if (tags === null) return;
|
|
3503
|
+
applyTags(tags);
|
|
3504
|
+
setEditingTag(null);
|
|
3505
|
+
} catch (error) {
|
|
3506
|
+
setLoadError(errorMessage(error));
|
|
3507
|
+
} finally {
|
|
3508
|
+
setTagBusy(false);
|
|
3509
|
+
}
|
|
3510
|
+
}, [api, applyTags]);
|
|
3511
|
+
/** 添加一个市场源(内置市场目录与手动输入共用),并立即扫描它。 */
|
|
3512
|
+
const addSource = (0, react.useCallback)(async (input) => {
|
|
3513
|
+
const value = input.trim();
|
|
3514
|
+
if (value === "") return;
|
|
3515
|
+
setLoadError(null);
|
|
3516
|
+
try {
|
|
3517
|
+
const result = await api.addMarketSource(value);
|
|
3518
|
+
setMarketState({
|
|
3519
|
+
status: "ready",
|
|
3520
|
+
repos: result.repos
|
|
3521
|
+
});
|
|
3522
|
+
setNewSourceName("");
|
|
3523
|
+
setRepoResult(null);
|
|
3524
|
+
setRepoSelected(/* @__PURE__ */ new Set());
|
|
3525
|
+
const slug = result.repos[result.repos.length - 1].repo;
|
|
3526
|
+
setScanningRepo(slug);
|
|
3527
|
+
setRepoDiscoverState({ status: "scanning" });
|
|
3528
|
+
try {
|
|
3529
|
+
const data = await api.repoDiscover(slug);
|
|
3530
|
+
setRepoDiscoverState({
|
|
3531
|
+
status: "ready",
|
|
3532
|
+
data
|
|
3533
|
+
});
|
|
3534
|
+
} catch (error) {
|
|
3535
|
+
setRepoDiscoverState({
|
|
3536
|
+
status: "error",
|
|
3537
|
+
message: errorMessage(error)
|
|
3538
|
+
});
|
|
3539
|
+
} finally {
|
|
3540
|
+
setScanningRepo(null);
|
|
3541
|
+
}
|
|
3542
|
+
} catch (error) {
|
|
3543
|
+
setLoadError(errorMessage(error));
|
|
3544
|
+
}
|
|
3545
|
+
}, [api]);
|
|
3546
|
+
/** 手动输入框的添加动作。 */
|
|
3547
|
+
const addMarketSource = (0, react.useCallback)(async () => {
|
|
3548
|
+
await addSource(newSourceName);
|
|
3549
|
+
}, [addSource, newSourceName]);
|
|
3550
|
+
/** 删除一个市场源(不影响已装技能)。 */
|
|
3551
|
+
const removeMarketSource = (0, react.useCallback)(async (repo) => {
|
|
3552
|
+
setTagBusy(true);
|
|
3553
|
+
setLoadError(null);
|
|
3554
|
+
try {
|
|
3555
|
+
const result = await api.removeMarketSource(repo);
|
|
3556
|
+
setMarketState({
|
|
3557
|
+
status: "ready",
|
|
3558
|
+
repos: result.repos
|
|
3559
|
+
});
|
|
3560
|
+
} catch (error) {
|
|
3561
|
+
setLoadError(errorMessage(error));
|
|
3562
|
+
} finally {
|
|
3563
|
+
setTagBusy(false);
|
|
3564
|
+
}
|
|
3565
|
+
}, [api]);
|
|
3566
|
+
/** 扫描一个市场源(或手动输入)的仓库。 */
|
|
3567
|
+
const scanRepo = (0, react.useCallback)(async (input) => {
|
|
3568
|
+
const value = input.trim();
|
|
3569
|
+
if (value === "") return;
|
|
3570
|
+
setRepoResult(null);
|
|
3571
|
+
setRepoSelected(/* @__PURE__ */ new Set());
|
|
3572
|
+
setScanningRepo(value);
|
|
3573
|
+
setRepoDiscoverState({ status: "scanning" });
|
|
3574
|
+
setLoadError(null);
|
|
3575
|
+
try {
|
|
3576
|
+
const data = await api.repoDiscover(value);
|
|
3577
|
+
if (data.ref === null) {
|
|
3578
|
+
setBranchChoice({
|
|
3579
|
+
repo: data.repo,
|
|
3580
|
+
branches: data.branches ?? [],
|
|
3581
|
+
selected: (data.branches ?? [])[0] ?? "main"
|
|
3582
|
+
});
|
|
3583
|
+
setRepoDiscoverState({ status: "idle" });
|
|
3584
|
+
return;
|
|
3585
|
+
}
|
|
3586
|
+
setRepoDiscoverState({
|
|
3587
|
+
status: "ready",
|
|
3588
|
+
data
|
|
3589
|
+
});
|
|
3590
|
+
} catch (error) {
|
|
3591
|
+
setRepoDiscoverState({
|
|
3592
|
+
status: "error",
|
|
3593
|
+
message: errorMessage(error)
|
|
3594
|
+
});
|
|
3595
|
+
} finally {
|
|
3596
|
+
setScanningRepo(null);
|
|
3597
|
+
}
|
|
3598
|
+
}, [api]);
|
|
3599
|
+
/** 确认分支选择:持久化 ref 后重新扫描。 */
|
|
3600
|
+
const confirmBranchChoice = (0, react.useCallback)(async () => {
|
|
3601
|
+
if (branchChoice === null) return;
|
|
3602
|
+
setBranchBusy(true);
|
|
3603
|
+
setLoadError(null);
|
|
3604
|
+
try {
|
|
3605
|
+
await api.setMarketSourceRef(branchChoice.repo, branchChoice.selected);
|
|
3606
|
+
setBranchChoice(null);
|
|
3607
|
+
await scanRepo(branchChoice.repo);
|
|
3608
|
+
} catch (error) {
|
|
3609
|
+
setLoadError(errorMessage(error));
|
|
3610
|
+
} finally {
|
|
3611
|
+
setBranchBusy(false);
|
|
3612
|
+
}
|
|
3613
|
+
}, [
|
|
3614
|
+
api,
|
|
3615
|
+
branchChoice,
|
|
3616
|
+
scanRepo
|
|
3617
|
+
]);
|
|
3618
|
+
/** Toggle one repo preview row. */
|
|
3619
|
+
const toggleRepoSelected = (0, react.useCallback)((path, checked) => {
|
|
3620
|
+
setRepoSelected((previous) => {
|
|
3621
|
+
const next = new Set(previous);
|
|
3622
|
+
if (checked) next.add(path);
|
|
3623
|
+
else next.delete(path);
|
|
3624
|
+
return next;
|
|
3625
|
+
});
|
|
3626
|
+
}, []);
|
|
3627
|
+
/** Import every checked, non-existing repo skill, then refresh the hub. */
|
|
3628
|
+
const importRepo = (0, react.useCallback)(async () => {
|
|
3629
|
+
if (repoDiscoverState.status !== "ready") return;
|
|
3630
|
+
setRepoImporting(true);
|
|
3631
|
+
setRepoResult(null);
|
|
3632
|
+
setLoadError(null);
|
|
3633
|
+
try {
|
|
3634
|
+
const result = await api.repoImport(repoDiscoverState.data.repo, [...repoSelected], repoDiscoverState.data.ref ?? void 0);
|
|
3635
|
+
setRepoResult(result);
|
|
3636
|
+
await Promise.all([
|
|
3637
|
+
load(),
|
|
3638
|
+
loadMarket(),
|
|
3639
|
+
loadGroups(),
|
|
3640
|
+
loadSources()
|
|
3641
|
+
]);
|
|
3642
|
+
} catch (error) {
|
|
3643
|
+
setLoadError(errorMessage(error));
|
|
3644
|
+
} finally {
|
|
3645
|
+
setRepoImporting(false);
|
|
3646
|
+
}
|
|
3647
|
+
}, [
|
|
3648
|
+
api,
|
|
3649
|
+
repoDiscoverState,
|
|
3650
|
+
repoSelected,
|
|
3651
|
+
load,
|
|
3652
|
+
loadMarket,
|
|
3653
|
+
loadGroups,
|
|
3654
|
+
loadSources
|
|
3655
|
+
]);
|
|
3656
|
+
/** 检查所有市场源的上游更新(服务端节流)。 */
|
|
3657
|
+
const checkMarket = (0, react.useCallback)(async () => {
|
|
3658
|
+
try {
|
|
3659
|
+
const result = await api.marketCheck();
|
|
3660
|
+
const next = {};
|
|
3661
|
+
for (const item of result.results) next[item.repo] = item;
|
|
3662
|
+
setMarketCheck(next);
|
|
3663
|
+
} catch {}
|
|
3664
|
+
}, [api]);
|
|
3665
|
+
/** 市场源同步:版本对齐后询问是否批量更新本地技能。 */
|
|
3666
|
+
const syncMarketSource = (0, react.useCallback)(async (repo) => {
|
|
3667
|
+
setSyncingMarket(repo);
|
|
3668
|
+
setLoadError(null);
|
|
3669
|
+
try {
|
|
3670
|
+
const result = await api.marketSync(repo);
|
|
3671
|
+
setMarketSyncDialog({
|
|
3672
|
+
repo: result.repo,
|
|
3673
|
+
ref: result.ref,
|
|
3674
|
+
skills: result.skills,
|
|
3675
|
+
selected: new Set(result.skills)
|
|
3676
|
+
});
|
|
3677
|
+
await loadMarket();
|
|
3678
|
+
} catch (error) {
|
|
3679
|
+
setLoadError(errorMessage(error));
|
|
3680
|
+
} finally {
|
|
3681
|
+
setSyncingMarket(null);
|
|
3682
|
+
}
|
|
3683
|
+
}, [api, loadMarket]);
|
|
3684
|
+
/** 批量更新本地技能到市场源当前版本(复用来源同步路径)。 */
|
|
3685
|
+
const confirmMarketSync = (0, react.useCallback)(async () => {
|
|
3686
|
+
if (marketSyncDialog === null) return;
|
|
3687
|
+
const selected = [...marketSyncDialog.selected];
|
|
3688
|
+
setSyncBusy(true);
|
|
1000
3689
|
setLoadError(null);
|
|
1001
3690
|
try {
|
|
1002
|
-
|
|
1003
|
-
|
|
3691
|
+
if (selected.length > 0) {
|
|
3692
|
+
const result = await api.syncSource(marketSyncDialog.repo, selected);
|
|
3693
|
+
if (result.failed.length > 0) setLoadError(result.failed.map((item) => item.name + ": " + item.error).join("\n"));
|
|
3694
|
+
}
|
|
3695
|
+
setMarketSyncDialog(null);
|
|
3696
|
+
await Promise.all([
|
|
3697
|
+
load(),
|
|
3698
|
+
loadGroups(),
|
|
3699
|
+
loadSources()
|
|
3700
|
+
]);
|
|
3701
|
+
checkMarket();
|
|
1004
3702
|
} catch (error) {
|
|
1005
3703
|
setLoadError(errorMessage(error));
|
|
1006
3704
|
} finally {
|
|
1007
|
-
|
|
1008
|
-
const next = new Set(previous);
|
|
1009
|
-
next.delete(record.name);
|
|
1010
|
-
return next;
|
|
1011
|
-
});
|
|
3705
|
+
setSyncBusy(false);
|
|
1012
3706
|
}
|
|
1013
|
-
}, [
|
|
3707
|
+
}, [
|
|
3708
|
+
api,
|
|
3709
|
+
marketSyncDialog,
|
|
3710
|
+
load,
|
|
3711
|
+
loadGroups,
|
|
3712
|
+
loadSources,
|
|
3713
|
+
checkMarket
|
|
3714
|
+
]);
|
|
3715
|
+
/**
|
|
3716
|
+
* 一键全部更新:把每个「有可更新技能」的来源逐个同步到上游最新版本
|
|
3717
|
+
* (复用来源同步路径,覆盖本地修改),随后刷新并重新检查。
|
|
3718
|
+
*/
|
|
3719
|
+
const updateAll = (0, react.useCallback)(async () => {
|
|
3720
|
+
setUpdateAllDialog(false);
|
|
3721
|
+
setBatchBusy(true);
|
|
3722
|
+
setLoadError(null);
|
|
3723
|
+
const failures = [];
|
|
3724
|
+
let done = 0;
|
|
3725
|
+
try {
|
|
3726
|
+
for (const [repo, check] of Object.entries(sourceCheck)) {
|
|
3727
|
+
if (!check.changed || check.updated.length === 0) continue;
|
|
3728
|
+
try {
|
|
3729
|
+
const result = await api.syncSource(repo, check.updated);
|
|
3730
|
+
done += 1;
|
|
3731
|
+
if (result.failed.length > 0) failures.push(tt("market.updateAllItem", {
|
|
3732
|
+
repo,
|
|
3733
|
+
count: result.failed.length
|
|
3734
|
+
}) + ": " + result.failed.map((item) => item.name + ": " + item.error).join("; "));
|
|
3735
|
+
} catch (error) {
|
|
3736
|
+
failures.push(repo + ": " + errorMessage(error));
|
|
3737
|
+
}
|
|
3738
|
+
}
|
|
3739
|
+
await Promise.all([
|
|
3740
|
+
load(),
|
|
3741
|
+
loadGroups(),
|
|
3742
|
+
loadSources()
|
|
3743
|
+
]);
|
|
3744
|
+
await checkSources();
|
|
3745
|
+
checkMarket();
|
|
3746
|
+
if (failures.length > 0) setLoadError(failures.join("\n"));
|
|
3747
|
+
else if (done > 0) setSuccessBanner(tt("market.updateAllDone", { count: done }));
|
|
3748
|
+
} finally {
|
|
3749
|
+
setBatchBusy(false);
|
|
3750
|
+
}
|
|
3751
|
+
}, [
|
|
3752
|
+
api,
|
|
3753
|
+
sourceCheck,
|
|
3754
|
+
checkSources,
|
|
3755
|
+
checkMarket,
|
|
3756
|
+
load,
|
|
3757
|
+
loadGroups,
|
|
3758
|
+
loadSources
|
|
3759
|
+
]);
|
|
3760
|
+
(0, react.useEffect)(() => {
|
|
3761
|
+
if (!shouldAutoCheck()) return;
|
|
3762
|
+
markAutoChecked();
|
|
3763
|
+
checkUpdate();
|
|
3764
|
+
checkSources();
|
|
3765
|
+
checkMarket();
|
|
3766
|
+
}, []);
|
|
1014
3767
|
const create = (0, react.useCallback)(async (event) => {
|
|
1015
3768
|
event.preventDefault();
|
|
1016
3769
|
setFormBusy(true);
|
|
1017
3770
|
setFormMessage(null);
|
|
3771
|
+
setSuccessBanner(null);
|
|
1018
3772
|
try {
|
|
1019
3773
|
const result = await api.create({
|
|
1020
3774
|
name: formName,
|
|
1021
3775
|
description: formDesc,
|
|
1022
3776
|
root: formRoot
|
|
1023
3777
|
});
|
|
1024
|
-
|
|
1025
|
-
kind: "success",
|
|
1026
|
-
text: tt("form.success") + result.path
|
|
1027
|
-
});
|
|
3778
|
+
setSuccessBanner(tt("form.success") + result.path);
|
|
1028
3779
|
setFormName("");
|
|
1029
3780
|
setFormDesc("");
|
|
1030
3781
|
setShowForm(false);
|
|
@@ -1045,195 +3796,310 @@ window.__ModuleLoader__.load({
|
|
|
1045
3796
|
load
|
|
1046
3797
|
]);
|
|
1047
3798
|
const normalized = search.trim().toLocaleLowerCase();
|
|
3799
|
+
/** skillName → origin repo(无来源记录的技能不在此映射中,筛选中视为 private)。 */
|
|
3800
|
+
const origins = groupsState?.origins ?? {};
|
|
3801
|
+
/** 来源筛选选项:来源记录中的仓库(排序)+ 末尾的「个人」;没有技能的仓库不列。 */
|
|
3802
|
+
const sourceOptions = (0, react.useMemo)(() => {
|
|
3803
|
+
const skills = catalog?.skills ?? [];
|
|
3804
|
+
const repos = [...new Set(skills.map((skill) => origins[skill.name]).filter((repo) => repo !== void 0))].sort();
|
|
3805
|
+
const hasPrivate = skills.some((skill) => origins[skill.name] === void 0);
|
|
3806
|
+
return [...repos, ...hasPrivate ? [PRIVATE_SOURCE] : []];
|
|
3807
|
+
}, [catalog, origins]);
|
|
1048
3808
|
const filtered = (0, react.useMemo)(() => (catalog?.skills ?? []).filter((skill) => normalized.length === 0 || skill.name.toLocaleLowerCase().includes(normalized) || skill.description.toLocaleLowerCase().includes(normalized)), [catalog, normalized]);
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
3809
|
+
return {
|
|
3810
|
+
catalog,
|
|
3811
|
+
loading,
|
|
3812
|
+
loadError,
|
|
3813
|
+
successBanner,
|
|
3814
|
+
updateState,
|
|
3815
|
+
repoDiscoverState,
|
|
3816
|
+
scanningRepo,
|
|
3817
|
+
repoSelected,
|
|
3818
|
+
repoImporting,
|
|
3819
|
+
repoResult,
|
|
3820
|
+
search,
|
|
3821
|
+
detail,
|
|
3822
|
+
detailLoading,
|
|
3823
|
+
busyNames,
|
|
3824
|
+
batchBusy,
|
|
3825
|
+
showForm,
|
|
3826
|
+
formName,
|
|
3827
|
+
formDesc,
|
|
3828
|
+
formRoot,
|
|
3829
|
+
formBusy,
|
|
3830
|
+
formMessage,
|
|
3831
|
+
uses,
|
|
3832
|
+
hubConfig,
|
|
3833
|
+
tab,
|
|
3834
|
+
skillView,
|
|
3835
|
+
sourceFilter,
|
|
3836
|
+
sortKey,
|
|
3837
|
+
marketState,
|
|
3838
|
+
marketCheck,
|
|
3839
|
+
branchChoice,
|
|
3840
|
+
branchBusy,
|
|
3841
|
+
marketSyncDialog,
|
|
3842
|
+
syncingMarket,
|
|
3843
|
+
syncBusy,
|
|
3844
|
+
newSourceName,
|
|
3845
|
+
groupsState,
|
|
3846
|
+
sourcesState,
|
|
3847
|
+
sourceCheck,
|
|
3848
|
+
checkingSource,
|
|
3849
|
+
syncingSource,
|
|
3850
|
+
conflictDialog,
|
|
3851
|
+
confirmDialog,
|
|
3852
|
+
deleteSkillDialog,
|
|
3853
|
+
confirmClearTrash,
|
|
3854
|
+
updateAllDialog,
|
|
3855
|
+
editingTag,
|
|
3856
|
+
editName,
|
|
3857
|
+
membersDraft,
|
|
3858
|
+
newTagName,
|
|
3859
|
+
tagBusy,
|
|
3860
|
+
editSearch,
|
|
3861
|
+
collapsedGroups,
|
|
3862
|
+
showLegend,
|
|
3863
|
+
actionNames,
|
|
3864
|
+
viewNames,
|
|
3865
|
+
normalized,
|
|
3866
|
+
origins,
|
|
3867
|
+
sourceOptions,
|
|
3868
|
+
filtered,
|
|
3869
|
+
sorted: (0, react.useMemo)(() => sortSkills(filtered, sortKey, (name) => uses.get(name)?.count), [
|
|
3870
|
+
filtered,
|
|
3871
|
+
sortKey,
|
|
3872
|
+
uses
|
|
3873
|
+
]),
|
|
3874
|
+
setLoadError,
|
|
3875
|
+
setSuccessBanner,
|
|
3876
|
+
setSearch,
|
|
3877
|
+
setDetail,
|
|
3878
|
+
setShowForm,
|
|
3879
|
+
setFormName,
|
|
3880
|
+
setFormDesc,
|
|
3881
|
+
setFormRoot,
|
|
3882
|
+
setFormMessage,
|
|
3883
|
+
setRepoSelected,
|
|
3884
|
+
setTab,
|
|
3885
|
+
setSkillView,
|
|
3886
|
+
setSourceFilter,
|
|
3887
|
+
setSortKey,
|
|
3888
|
+
setBranchChoice,
|
|
3889
|
+
setMarketSyncDialog,
|
|
3890
|
+
setNewSourceName,
|
|
3891
|
+
setConflictDialog,
|
|
3892
|
+
setConfirmDialog,
|
|
3893
|
+
setDeleteSkillDialog,
|
|
3894
|
+
setConfirmClearTrash,
|
|
3895
|
+
setUpdateAllDialog,
|
|
3896
|
+
setEditingTag,
|
|
3897
|
+
setEditName,
|
|
3898
|
+
setMembersDraft,
|
|
3899
|
+
setNewTagName,
|
|
3900
|
+
setEditSearch,
|
|
3901
|
+
setShowLegend,
|
|
3902
|
+
toggleGroupCollapse,
|
|
3903
|
+
checkUpdate,
|
|
3904
|
+
loadMarket,
|
|
3905
|
+
openDetail,
|
|
3906
|
+
toggle,
|
|
3907
|
+
enableDisabled,
|
|
3908
|
+
batchToggleNames,
|
|
3909
|
+
toggleGroup,
|
|
3910
|
+
resolveConflict,
|
|
3911
|
+
runConfirmed,
|
|
3912
|
+
checkSources,
|
|
3913
|
+
requestSync,
|
|
3914
|
+
requestDelete,
|
|
3915
|
+
restoreTrash,
|
|
3916
|
+
clearTrash,
|
|
3917
|
+
requestDeleteSkill,
|
|
3918
|
+
runDeleteSkill,
|
|
3919
|
+
createTag,
|
|
3920
|
+
deleteTag,
|
|
3921
|
+
saveTag,
|
|
3922
|
+
addSource,
|
|
3923
|
+
addMarketSource,
|
|
3924
|
+
removeMarketSource,
|
|
3925
|
+
scanRepo,
|
|
3926
|
+
confirmBranchChoice,
|
|
3927
|
+
toggleRepoSelected,
|
|
3928
|
+
importRepo,
|
|
3929
|
+
checkMarket,
|
|
3930
|
+
syncMarketSource,
|
|
3931
|
+
confirmMarketSync,
|
|
3932
|
+
updateAll,
|
|
3933
|
+
create
|
|
1118
3934
|
};
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
detail.path
|
|
1174
|
-
]
|
|
1175
|
-
}) : null
|
|
1176
|
-
]
|
|
1177
|
-
}),
|
|
1178
|
-
detailLoading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1179
|
-
className: panel_module_css_default.muted,
|
|
1180
|
-
children: tt("detail.loading")
|
|
1181
|
-
}) : null,
|
|
1182
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
1183
|
-
className: panel_module_css_default.detailContent,
|
|
1184
|
-
children: detail.content
|
|
1185
|
-
})
|
|
1186
|
-
]
|
|
3935
|
+
}
|
|
3936
|
+
//#endregion
|
|
3937
|
+
//#region src/client/panel/SkillHubPanel.tsx
|
|
3938
|
+
function SkillHubPanel(props) {
|
|
3939
|
+
const hub = useSkillHub(props.api);
|
|
3940
|
+
const { catalog, loading, loadError, successBanner, updateState, detail, detailLoading, showForm, formName, formDesc, formRoot, formBusy, formMessage, hubConfig, tab, skillView, sourceFilter, sortKey, search, sourcesState, tagBusy, busyNames, normalized, origins, sourceOptions, filtered, conflictDialog, confirmDialog, deleteSkillDialog, confirmClearTrash, branchChoice, branchBusy, marketSyncDialog, syncBusy, editingTag, editName, membersDraft, editSearch, uses, groupsState, sourceCheck, checkingSource, syncingSource, showLegend, setLoadError, setSuccessBanner, setDetail, setShowForm, setFormName, setFormDesc, setFormRoot, setFormMessage, setTab, setSkillView, setSourceFilter, setSortKey, setSearch, setConflictDialog, setConfirmDialog, setDeleteSkillDialog, setConfirmClearTrash, setBranchChoice, setMarketSyncDialog, setEditingTag, setEditName, setMembersDraft, setEditSearch, setShowLegend, checkUpdate, loadMarket, checkSources, requestSync, requestDelete, restoreTrash, clearTrash, runDeleteSkill, runConfirmed, resolveConflict, confirmBranchChoice, confirmMarketSync, create, saveTag, deleteTag, enableDisabled } = hub;
|
|
3941
|
+
if (detail !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillDetailView, {
|
|
3942
|
+
detail,
|
|
3943
|
+
hubConfig,
|
|
3944
|
+
uses,
|
|
3945
|
+
groupsState,
|
|
3946
|
+
sourcesState,
|
|
3947
|
+
sourceCheck,
|
|
3948
|
+
checkingSource,
|
|
3949
|
+
syncingSource,
|
|
3950
|
+
loading: detailLoading,
|
|
3951
|
+
onBack: () => {
|
|
3952
|
+
setDetail(null);
|
|
3953
|
+
},
|
|
3954
|
+
onCheck: (repo) => {
|
|
3955
|
+
checkSources(repo);
|
|
3956
|
+
},
|
|
3957
|
+
onSync: requestSync,
|
|
3958
|
+
onFollowDelete: requestDelete
|
|
3959
|
+
});
|
|
3960
|
+
if (editingTag !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TagEditorView, {
|
|
3961
|
+
tag: editingTag,
|
|
3962
|
+
editName,
|
|
3963
|
+
membersDraft,
|
|
3964
|
+
editSearch,
|
|
3965
|
+
catalog,
|
|
3966
|
+
tagBusy,
|
|
3967
|
+
onBack: () => {
|
|
3968
|
+
setEditingTag(null);
|
|
3969
|
+
},
|
|
3970
|
+
onEditName: setEditName,
|
|
3971
|
+
onEditSearch: setEditSearch,
|
|
3972
|
+
onToggleMember: (name, checked) => {
|
|
3973
|
+
setMembersDraft((previous) => {
|
|
3974
|
+
const next = new Set(previous);
|
|
3975
|
+
if (checked) next.add(name);
|
|
3976
|
+
else next.delete(name);
|
|
3977
|
+
return next;
|
|
3978
|
+
});
|
|
3979
|
+
},
|
|
3980
|
+
onRename: () => {
|
|
3981
|
+
saveTag(editingTag.id, editName, null);
|
|
3982
|
+
},
|
|
3983
|
+
onDelete: () => {
|
|
3984
|
+
deleteTag(editingTag.id);
|
|
3985
|
+
},
|
|
3986
|
+
onSaveMembers: () => {
|
|
3987
|
+
saveTag(editingTag.id, editName, [...membersDraft]);
|
|
3988
|
+
}
|
|
1187
3989
|
});
|
|
3990
|
+
/** 检查结果的悬停提示:收敛到「检查更新」按钮上,不再占面板顶部横幅。 */
|
|
3991
|
+
const updateTitle = (() => {
|
|
3992
|
+
if (updateState.status === "checking") return tt("update.checking");
|
|
3993
|
+
if (updateState.status === "error") return tt("update.error", { error: updateState.message });
|
|
3994
|
+
if (updateState.status === "ready") {
|
|
3995
|
+
const data = updateState.data;
|
|
3996
|
+
if (data.error !== void 0) return tt("update.error", { error: data.error });
|
|
3997
|
+
if (data.updateAvailable) return tt("update.available", {
|
|
3998
|
+
version: data.latestVersion ?? "",
|
|
3999
|
+
current: data.currentVersion
|
|
4000
|
+
});
|
|
4001
|
+
if (data.latestVersion === null) return tt("update.unavailable");
|
|
4002
|
+
return tt("update.upToDate", { version: data.latestVersion });
|
|
4003
|
+
}
|
|
4004
|
+
})();
|
|
1188
4005
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1189
4006
|
className: panel_module_css_default.panel,
|
|
1190
4007
|
children: [
|
|
1191
4008
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1192
4009
|
className: panel_module_css_default.header,
|
|
1193
4010
|
children: [
|
|
1194
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
4011
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h2", {
|
|
1195
4012
|
className: panel_module_css_default.title,
|
|
1196
|
-
children:
|
|
4013
|
+
children: [
|
|
4014
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSkillOutline16, {
|
|
4015
|
+
size: 16,
|
|
4016
|
+
className: panel_module_css_default.titleIcon
|
|
4017
|
+
}),
|
|
4018
|
+
" ",
|
|
4019
|
+
tt("panel.title")
|
|
4020
|
+
]
|
|
1197
4021
|
}),
|
|
4022
|
+
catalog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4023
|
+
className: panel_module_css_default.headerCount,
|
|
4024
|
+
children: [tt("panel.count", { count: catalog.skills.length + catalog.disabled.length }), catalog.disabled.length > 0 ? " · " + tt("panel.disabledCount", { count: catalog.disabled.length }) : null]
|
|
4025
|
+
}) : null,
|
|
1198
4026
|
catalog !== null && !catalog.complete ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1199
4027
|
className: panel_module_css_default.hint,
|
|
1200
4028
|
children: tt("panel.incomplete")
|
|
1201
4029
|
}) : null,
|
|
1202
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1203
|
-
className: panel_module_css_default.segmented,
|
|
1204
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1205
|
-
type: "button",
|
|
1206
|
-
className: panel_module_css_default.segBtn + (grouping === "source" ? " " + panel_module_css_default.segBtnActive : ""),
|
|
1207
|
-
onClick: () => {
|
|
1208
|
-
setGrouping("source");
|
|
1209
|
-
},
|
|
1210
|
-
children: tt("view.source")
|
|
1211
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1212
|
-
type: "button",
|
|
1213
|
-
className: panel_module_css_default.segBtn + (grouping === "sets" ? " " + panel_module_css_default.segBtnActive : ""),
|
|
1214
|
-
onClick: () => {
|
|
1215
|
-
setGrouping("sets");
|
|
1216
|
-
},
|
|
1217
|
-
children: tt("view.sets")
|
|
1218
|
-
})]
|
|
1219
|
-
}),
|
|
1220
4030
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1221
4031
|
className: panel_module_css_default.actions,
|
|
1222
4032
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1223
4033
|
type: "button",
|
|
1224
4034
|
className: panel_module_css_default.button,
|
|
4035
|
+
disabled: updateState.status === "checking",
|
|
4036
|
+
title: updateTitle,
|
|
1225
4037
|
onClick: () => {
|
|
1226
|
-
|
|
4038
|
+
checkUpdate();
|
|
1227
4039
|
},
|
|
1228
|
-
children: tt("
|
|
1229
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
4040
|
+
children: updateState.status === "checking" ? tt("update.checking") : tt("update.check")
|
|
4041
|
+
}), updateState.status === "ready" && updateState.data.updateAvailable && updateState.data.url !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
4042
|
+
className: panel_module_css_default.updateLink,
|
|
4043
|
+
href: updateState.data.url,
|
|
4044
|
+
target: "_blank",
|
|
4045
|
+
rel: "noreferrer",
|
|
4046
|
+
children: tt("update.newVersion", { version: updateState.data.latestVersion ?? "" })
|
|
4047
|
+
}) : null]
|
|
4048
|
+
})
|
|
4049
|
+
]
|
|
4050
|
+
}),
|
|
4051
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4052
|
+
className: panel_module_css_default.subbar,
|
|
4053
|
+
children: [
|
|
4054
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4055
|
+
className: panel_module_css_default.segmented,
|
|
4056
|
+
children: [
|
|
4057
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4058
|
+
type: "button",
|
|
4059
|
+
className: panel_module_css_default.segBtn + (tab === "sources" ? " " + panel_module_css_default.segBtnActive : ""),
|
|
4060
|
+
onClick: () => {
|
|
4061
|
+
setTab("sources");
|
|
4062
|
+
},
|
|
4063
|
+
children: tt("view.sources")
|
|
4064
|
+
}),
|
|
4065
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4066
|
+
type: "button",
|
|
4067
|
+
className: panel_module_css_default.segBtn + (tab === "scenes" ? " " + panel_module_css_default.segBtnActive : ""),
|
|
4068
|
+
onClick: () => {
|
|
4069
|
+
setTab("scenes");
|
|
4070
|
+
},
|
|
4071
|
+
children: tt("view.scenes")
|
|
4072
|
+
}),
|
|
4073
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4074
|
+
type: "button",
|
|
4075
|
+
className: panel_module_css_default.segBtn + (tab === "market" ? " " + panel_module_css_default.segBtnActive : ""),
|
|
4076
|
+
onClick: () => {
|
|
4077
|
+
setTab("market");
|
|
4078
|
+
loadMarket();
|
|
4079
|
+
},
|
|
4080
|
+
children: tt("view.market")
|
|
4081
|
+
})
|
|
4082
|
+
]
|
|
4083
|
+
}),
|
|
4084
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4085
|
+
type: "button",
|
|
4086
|
+
className: panel_module_css_default.legendToggle + (showLegend ? " " + panel_module_css_default.legendToggleActive : ""),
|
|
4087
|
+
onClick: () => {
|
|
4088
|
+
setShowLegend((value) => !value);
|
|
4089
|
+
},
|
|
4090
|
+
title: tt("legend.hint"),
|
|
4091
|
+
children: "?"
|
|
4092
|
+
}),
|
|
4093
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4094
|
+
className: panel_module_css_default.actions,
|
|
4095
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1230
4096
|
type: "button",
|
|
1231
4097
|
className: panel_module_css_default.button + " " + panel_module_css_default.primary,
|
|
1232
4098
|
onClick: () => {
|
|
1233
4099
|
setShowForm((value) => !value);
|
|
1234
4100
|
},
|
|
1235
4101
|
children: tt("panel.new")
|
|
1236
|
-
})
|
|
4102
|
+
})
|
|
1237
4103
|
})
|
|
1238
4104
|
]
|
|
1239
4105
|
}),
|
|
@@ -1243,6 +4109,10 @@ window.__ModuleLoader__.load({
|
|
|
1243
4109
|
create(event);
|
|
1244
4110
|
},
|
|
1245
4111
|
children: [
|
|
4112
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4113
|
+
className: panel_module_css_default.hintLine,
|
|
4114
|
+
children: tt("form.capabilityHint")
|
|
4115
|
+
}),
|
|
1246
4116
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1247
4117
|
className: panel_module_css_default.formRow,
|
|
1248
4118
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
@@ -1267,7 +4137,8 @@ window.__ModuleLoader__.load({
|
|
|
1267
4137
|
value: formDesc,
|
|
1268
4138
|
onChange: (event) => {
|
|
1269
4139
|
setFormDesc(event.target.value);
|
|
1270
|
-
}
|
|
4140
|
+
},
|
|
4141
|
+
placeholder: tt("form.descPlaceholder")
|
|
1271
4142
|
})]
|
|
1272
4143
|
}),
|
|
1273
4144
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1324,89 +4195,172 @@ window.__ModuleLoader__.load({
|
|
|
1324
4195
|
children: tt("err.dismiss")
|
|
1325
4196
|
})]
|
|
1326
4197
|
}) : null,
|
|
1327
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1328
|
-
className: panel_module_css_default.
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
4198
|
+
successBanner !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4199
|
+
className: panel_module_css_default.successBanner,
|
|
4200
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: successBanner }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4201
|
+
type: "button",
|
|
4202
|
+
className: panel_module_css_default.button,
|
|
4203
|
+
onClick: () => {
|
|
4204
|
+
setSuccessBanner(null);
|
|
4205
|
+
},
|
|
4206
|
+
children: tt("err.dismiss")
|
|
4207
|
+
})]
|
|
4208
|
+
}) : null,
|
|
4209
|
+
showLegend ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4210
|
+
className: panel_module_css_default.legend,
|
|
4211
|
+
children: [
|
|
4212
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4213
|
+
className: panel_module_css_default.legendItem,
|
|
4214
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4215
|
+
className: panel_module_css_default.dot + " " + panel_module_css_default.dotModel,
|
|
4216
|
+
style: dotStyle(hubConfig?.dotModelColor)
|
|
4217
|
+
}), tt("legend.model")]
|
|
4218
|
+
}),
|
|
4219
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4220
|
+
className: panel_module_css_default.legendItem,
|
|
4221
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4222
|
+
className: panel_module_css_default.dot + " " + panel_module_css_default.dotUser,
|
|
4223
|
+
style: dotStyle(hubConfig?.dotUserColor)
|
|
4224
|
+
}), tt("legend.user")]
|
|
4225
|
+
}),
|
|
4226
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4227
|
+
className: panel_module_css_default.legendHint,
|
|
4228
|
+
children: tt("legend.hint")
|
|
4229
|
+
})
|
|
4230
|
+
]
|
|
4231
|
+
}) : null,
|
|
1335
4232
|
loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1336
4233
|
className: panel_module_css_default.empty,
|
|
1337
4234
|
children: tt("panel.loading")
|
|
1338
4235
|
}) : null,
|
|
1339
|
-
|
|
4236
|
+
detailLoading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4237
|
+
className: panel_module_css_default.empty,
|
|
4238
|
+
children: tt("detail.loading")
|
|
4239
|
+
}) : null,
|
|
4240
|
+
tab === "market" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarketView, { hub }) : catalog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
4241
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4242
|
+
className: panel_module_css_default.filterBar,
|
|
4243
|
+
children: [
|
|
4244
|
+
tab === "sources" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4245
|
+
className: panel_module_css_default.segmented,
|
|
4246
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4247
|
+
type: "button",
|
|
4248
|
+
className: panel_module_css_default.segBtn + (skillView === "flat" ? " " + panel_module_css_default.segBtnActive : ""),
|
|
4249
|
+
onClick: () => {
|
|
4250
|
+
setSkillView("flat");
|
|
4251
|
+
},
|
|
4252
|
+
children: tt("view.flat")
|
|
4253
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4254
|
+
type: "button",
|
|
4255
|
+
className: panel_module_css_default.segBtn + (skillView === "groups" ? " " + panel_module_css_default.segBtnActive : ""),
|
|
4256
|
+
onClick: () => {
|
|
4257
|
+
setSkillView("groups");
|
|
4258
|
+
},
|
|
4259
|
+
children: tt("view.grouped")
|
|
4260
|
+
})]
|
|
4261
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
4262
|
+
className: panel_module_css_default.select,
|
|
4263
|
+
value: sourceFilter,
|
|
4264
|
+
onChange: (event) => {
|
|
4265
|
+
setSourceFilter(event.target.value);
|
|
4266
|
+
},
|
|
4267
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
4268
|
+
value: "all",
|
|
4269
|
+
children: tt("filter.allSources")
|
|
4270
|
+
}), sourceOptions.map((source) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
4271
|
+
value: source,
|
|
4272
|
+
children: source === "private" ? tt("filter.private") : source
|
|
4273
|
+
}, source))]
|
|
4274
|
+
})] }) : null,
|
|
4275
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
4276
|
+
className: panel_module_css_default.select,
|
|
4277
|
+
value: sortKey,
|
|
4278
|
+
onChange: (event) => {
|
|
4279
|
+
setSortKey(event.target.value);
|
|
4280
|
+
},
|
|
4281
|
+
children: [
|
|
4282
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
4283
|
+
value: "name",
|
|
4284
|
+
children: tt("sort.name")
|
|
4285
|
+
}),
|
|
4286
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
4287
|
+
value: "added",
|
|
4288
|
+
children: tt("sort.added")
|
|
4289
|
+
}),
|
|
4290
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
4291
|
+
value: "uses",
|
|
4292
|
+
children: tt("sort.uses")
|
|
4293
|
+
})
|
|
4294
|
+
]
|
|
4295
|
+
}),
|
|
4296
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4297
|
+
className: panel_module_css_default.search,
|
|
4298
|
+
value: search,
|
|
4299
|
+
onChange: (event) => {
|
|
4300
|
+
setSearch(event.target.value);
|
|
4301
|
+
},
|
|
4302
|
+
placeholder: tt("panel.search")
|
|
4303
|
+
})
|
|
4304
|
+
]
|
|
4305
|
+
}),
|
|
1340
4306
|
filtered.length === 0 && search.trim() !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1341
4307
|
className: panel_module_css_default.empty,
|
|
1342
4308
|
children: tt("panel.empty")
|
|
1343
4309
|
}) : null,
|
|
1344
|
-
filtered.length === 0 && search.trim() === "" && catalog.disabled.length === 0 && catalog.diagnostics.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4310
|
+
filtered.length === 0 && search.trim() === "" && catalog.skills.length === 0 && catalog.disabled.length === 0 && catalog.diagnostics.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1345
4311
|
className: panel_module_css_default.empty,
|
|
1346
4312
|
children: tt("panel.emptyAll")
|
|
1347
4313
|
}) : null,
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
if (skills === void 0 || skills.length === 0) return null;
|
|
1351
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1352
|
-
className: panel_module_css_default.section,
|
|
1353
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1354
|
-
className: panel_module_css_default.groupTitle,
|
|
1355
|
-
children: tt(groupKey(source))
|
|
1356
|
-
}), skills.map(renderRow)]
|
|
1357
|
-
}, source);
|
|
1358
|
-
}), (() => {
|
|
1359
|
-
const rest = filtered.filter((skill) => !SOURCE_GROUPS.includes(skill.source));
|
|
1360
|
-
if (rest.length === 0) return null;
|
|
1361
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1362
|
-
className: panel_module_css_default.section,
|
|
1363
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1364
|
-
className: panel_module_css_default.groupTitle,
|
|
1365
|
-
children: tt("group.other")
|
|
1366
|
-
}), rest.map(renderRow)]
|
|
1367
|
-
});
|
|
1368
|
-
})()] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [[...bySet.keys()].filter((set) => set !== "").sort().map((set) => {
|
|
1369
|
-
const skills = bySet.get(set);
|
|
1370
|
-
if (skills === void 0 || skills.length === 0) return null;
|
|
1371
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1372
|
-
className: panel_module_css_default.section,
|
|
1373
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1374
|
-
className: panel_module_css_default.groupTitle,
|
|
1375
|
-
children: set
|
|
1376
|
-
}), skills.map(renderRow)]
|
|
1377
|
-
}, set);
|
|
1378
|
-
}), bySet.has("") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1379
|
-
className: panel_module_css_default.section,
|
|
1380
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1381
|
-
className: panel_module_css_default.groupTitle,
|
|
1382
|
-
children: tt("group.uncategorized")
|
|
1383
|
-
}), bySet.get("").map(renderRow)]
|
|
1384
|
-
}) : null] }),
|
|
1385
|
-
catalog.disabled.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4314
|
+
tab === "sources" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SourcesView, { hub }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScenesView, { hub }),
|
|
4315
|
+
sourcesState !== null && sourcesState.trash.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1386
4316
|
className: panel_module_css_default.section,
|
|
1387
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1388
|
-
className: panel_module_css_default.sectionTitle,
|
|
1389
|
-
children:
|
|
1390
|
-
|
|
1391
|
-
|
|
4317
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4318
|
+
className: panel_module_css_default.sectionTitle + " " + panel_module_css_default.sectionHeadRow,
|
|
4319
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4320
|
+
className: panel_module_css_default.sectionTitleFill,
|
|
4321
|
+
children: tt("source.trash")
|
|
4322
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4323
|
+
type: "button",
|
|
4324
|
+
className: panel_module_css_default.opBtn + " " + panel_module_css_default.opDanger,
|
|
4325
|
+
disabled: tagBusy,
|
|
4326
|
+
onClick: () => {
|
|
4327
|
+
setConfirmClearTrash(true);
|
|
4328
|
+
},
|
|
4329
|
+
children: tt("source.clearTrash")
|
|
4330
|
+
})]
|
|
4331
|
+
}), sourcesState.trash.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4332
|
+
className: panel_module_css_default.row + " " + panel_module_css_default.rowStatic,
|
|
1392
4333
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1393
4334
|
className: panel_module_css_default.rowMain,
|
|
1394
4335
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1395
4336
|
className: panel_module_css_default.rowName,
|
|
1396
|
-
children:
|
|
4337
|
+
children: entry.name
|
|
1397
4338
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1398
4339
|
className: panel_module_css_default.rowDesc,
|
|
1399
|
-
children:
|
|
4340
|
+
children: relativeTimeText(entry.movedAt)
|
|
1400
4341
|
})]
|
|
1401
4342
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1402
4343
|
type: "button",
|
|
1403
|
-
className: panel_module_css_default.
|
|
1404
|
-
disabled:
|
|
4344
|
+
className: panel_module_css_default.opBtn,
|
|
4345
|
+
disabled: tagBusy,
|
|
1405
4346
|
onClick: () => {
|
|
1406
|
-
|
|
4347
|
+
restoreTrash(entry.name);
|
|
1407
4348
|
},
|
|
1408
|
-
children: tt("
|
|
4349
|
+
children: tt("source.restore")
|
|
1409
4350
|
})]
|
|
4351
|
+
}, entry.name))]
|
|
4352
|
+
}) : null,
|
|
4353
|
+
catalog.disabled.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4354
|
+
className: panel_module_css_default.section,
|
|
4355
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4356
|
+
className: panel_module_css_default.sectionTitle,
|
|
4357
|
+
children: tt("panel.disabled")
|
|
4358
|
+
}), catalog.disabled.filter((record) => (normalized.length === 0 || record.name.toLocaleLowerCase().includes(normalized) || record.description.toLocaleLowerCase().includes(normalized)) && (sourceFilter === "all" || (origins[record.name] ?? "private") === sourceFilter)).map((record) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DisabledRow, {
|
|
4359
|
+
record,
|
|
4360
|
+
busy: busyNames.has(record.name),
|
|
4361
|
+
onEnable: () => {
|
|
4362
|
+
enableDisabled(record);
|
|
4363
|
+
}
|
|
1410
4364
|
}, record.name))]
|
|
1411
4365
|
}) : null,
|
|
1412
4366
|
catalog.diagnostics.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
@@ -1425,7 +4379,93 @@ window.__ModuleLoader__.load({
|
|
|
1425
4379
|
})]
|
|
1426
4380
|
}, entry.path))]
|
|
1427
4381
|
}) : null
|
|
1428
|
-
] }) : null
|
|
4382
|
+
] }) : null,
|
|
4383
|
+
conflictDialog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConflictDialog, {
|
|
4384
|
+
dialog: conflictDialog,
|
|
4385
|
+
tags: groupsState?.tags ?? [],
|
|
4386
|
+
collections: groupsState?.collections ?? [],
|
|
4387
|
+
onClose: () => {
|
|
4388
|
+
setConflictDialog(null);
|
|
4389
|
+
},
|
|
4390
|
+
onKeepOn: () => {
|
|
4391
|
+
resolveConflict(false);
|
|
4392
|
+
},
|
|
4393
|
+
onCloseAll: () => {
|
|
4394
|
+
resolveConflict(true);
|
|
4395
|
+
}
|
|
4396
|
+
}) : null,
|
|
4397
|
+
confirmDialog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmDialog, {
|
|
4398
|
+
title: confirmDialog.kind === "sync" ? tt("source.syncConfirmTitle") : tt("source.deleteConfirmTitle"),
|
|
4399
|
+
text: confirmDialog.kind === "sync" ? tt("source.syncConfirmText") : tt("source.deleteConfirmText"),
|
|
4400
|
+
items: confirmDialog.skills,
|
|
4401
|
+
confirmLabel: confirmDialog.kind === "sync" ? tt("source.sync") : tt("source.followDelete"),
|
|
4402
|
+
danger: confirmDialog.kind === "delete",
|
|
4403
|
+
onCancel: () => {
|
|
4404
|
+
setConfirmDialog(null);
|
|
4405
|
+
},
|
|
4406
|
+
onConfirm: () => {
|
|
4407
|
+
runConfirmed();
|
|
4408
|
+
}
|
|
4409
|
+
}) : null,
|
|
4410
|
+
branchChoice !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BranchChoiceDialog, {
|
|
4411
|
+
choice: branchChoice,
|
|
4412
|
+
busy: branchBusy,
|
|
4413
|
+
onSelect: (selected) => {
|
|
4414
|
+
setBranchChoice({
|
|
4415
|
+
...branchChoice,
|
|
4416
|
+
selected
|
|
4417
|
+
});
|
|
4418
|
+
},
|
|
4419
|
+
onCancel: () => {
|
|
4420
|
+
setBranchChoice(null);
|
|
4421
|
+
},
|
|
4422
|
+
onConfirm: () => {
|
|
4423
|
+
confirmBranchChoice();
|
|
4424
|
+
}
|
|
4425
|
+
}) : null,
|
|
4426
|
+
marketSyncDialog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarketSyncDialog, {
|
|
4427
|
+
dialog: marketSyncDialog,
|
|
4428
|
+
busy: syncBusy,
|
|
4429
|
+
onToggle: (name, checked) => {
|
|
4430
|
+
const next = new Set(marketSyncDialog.selected);
|
|
4431
|
+
if (checked) next.add(name);
|
|
4432
|
+
else next.delete(name);
|
|
4433
|
+
setMarketSyncDialog({
|
|
4434
|
+
...marketSyncDialog,
|
|
4435
|
+
selected: next
|
|
4436
|
+
});
|
|
4437
|
+
},
|
|
4438
|
+
onCancel: () => {
|
|
4439
|
+
setMarketSyncDialog(null);
|
|
4440
|
+
},
|
|
4441
|
+
onConfirm: () => {
|
|
4442
|
+
confirmMarketSync();
|
|
4443
|
+
}
|
|
4444
|
+
}) : null,
|
|
4445
|
+
deleteSkillDialog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmDialog, {
|
|
4446
|
+
title: tt("delete.confirmTitle"),
|
|
4447
|
+
text: tt("delete.confirmText", { name: deleteSkillDialog }),
|
|
4448
|
+
confirmLabel: tt("delete.confirm"),
|
|
4449
|
+
danger: true,
|
|
4450
|
+
onCancel: () => {
|
|
4451
|
+
setDeleteSkillDialog(null);
|
|
4452
|
+
},
|
|
4453
|
+
onConfirm: () => {
|
|
4454
|
+
runDeleteSkill();
|
|
4455
|
+
}
|
|
4456
|
+
}) : null,
|
|
4457
|
+
confirmClearTrash ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmDialog, {
|
|
4458
|
+
title: tt("source.clearTrashConfirmTitle"),
|
|
4459
|
+
text: tt("source.clearTrashConfirmText"),
|
|
4460
|
+
confirmLabel: tt("source.clearTrashConfirm"),
|
|
4461
|
+
danger: true,
|
|
4462
|
+
onCancel: () => {
|
|
4463
|
+
setConfirmClearTrash(false);
|
|
4464
|
+
},
|
|
4465
|
+
onConfirm: () => {
|
|
4466
|
+
clearTrash();
|
|
4467
|
+
}
|
|
4468
|
+
}) : null
|
|
1429
4469
|
]
|
|
1430
4470
|
});
|
|
1431
4471
|
}
|
|
@@ -1456,12 +4496,13 @@ window.__ModuleLoader__.load({
|
|
|
1456
4496
|
}, SkillHubSettingsCard)), "dsh-skill-hub: settings card");
|
|
1457
4497
|
ctx.effect(() => ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
1458
4498
|
name: "settings.section",
|
|
1459
|
-
id: "
|
|
4499
|
+
id: "skill-hub",
|
|
1460
4500
|
order: 12,
|
|
1461
4501
|
label: () => t("entry.label"),
|
|
1462
4502
|
locale: NS,
|
|
1463
4503
|
inject: () => ({})
|
|
1464
4504
|
}, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillHubPanel, { api }))), "dsh-skill-hub: settings section");
|
|
4505
|
+
ctx.effect(() => applySettingsNavIcon(), "dsh-skill-hub: settings nav icon");
|
|
1465
4506
|
}
|
|
1466
4507
|
//#endregion
|
|
1467
4508
|
exports.apply = apply;
|