dsh-skill-hub 0.3.7 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -50
- package/README.zh.md +38 -48
- package/lib/client.js +2209 -1820
- package/lib/client.js.map +1 -1
- package/lib/index.js +653 -419
- package/lib/types/client/SkillHubSettingsCard.d.ts +1 -0
- package/lib/types/client/api.d.ts +12 -6
- package/lib/types/client/helpers.d.ts +0 -1
- package/lib/types/client/locales/common.d.ts +52 -0
- package/lib/types/client/locales/detail.d.ts +16 -0
- package/lib/types/client/locales/market.d.ts +73 -0
- package/lib/types/client/locales/settings.d.ts +41 -0
- package/lib/types/client/locales/skills.d.ts +65 -0
- package/lib/types/client/locales/sources.d.ts +35 -0
- package/lib/types/client/locales.d.ts +124 -118
- package/lib/types/client/panel/MarketSourceRow.d.ts +12 -0
- package/lib/types/client/panel/MarketView.d.ts +1 -0
- package/lib/types/client/panel/RepoScanCard.d.ts +10 -0
- package/lib/types/client/panel/hooks/shared.d.ts +72 -0
- package/lib/types/client/panel/hooks/useCatalogFlow.d.ts +62 -0
- package/lib/types/client/panel/hooks/useGroupFlow.d.ts +41 -0
- package/lib/types/client/panel/hooks/useMarketFlow.d.ts +73 -0
- package/lib/types/client/panel/hooks/useMetaFlow.d.ts +18 -0
- package/lib/types/client/panel/hooks/useSourceFlow.d.ts +44 -0
- package/lib/types/client/panel/market-format.d.ts +6 -0
- package/lib/types/client/panel/useSkillHub.d.ts +45 -73
- package/lib/types/client/settings-card.d.ts +20 -0
- package/lib/types/client/settings-form.d.ts +6 -0
- package/lib/types/protocol/api.d.ts +38 -0
- package/lib/types/protocol/catalog.d.ts +155 -0
- package/lib/types/protocol/config.d.ts +113 -0
- package/lib/types/protocol/groups.d.ts +95 -0
- package/lib/types/protocol/market.d.ts +94 -0
- package/lib/types/protocol/repo.d.ts +107 -0
- package/lib/types/protocol/sources.d.ts +139 -0
- package/lib/types/protocol/stats.d.ts +46 -0
- package/lib/types/protocol.d.ts +13 -743
- package/lib/types/repo/github-client.d.ts +42 -0
- package/lib/types/repo.d.ts +6 -17
- package/lib/types/routes/catalog.d.ts +7 -0
- package/lib/types/routes/config.d.ts +7 -0
- package/lib/types/routes/groups.d.ts +7 -0
- package/lib/types/routes/helpers.d.ts +145 -0
- package/lib/types/routes/market.d.ts +8 -0
- package/lib/types/routes/repo-import.d.ts +7 -0
- package/lib/types/routes/route-state.d.ts +55 -0
- package/lib/types/routes/sources.d.ts +8 -0
- package/lib/types/routes.d.ts +5 -34
- package/lib/types/stats.d.ts +3 -2
- package/lib/types/store/errors.d.ts +10 -0
- package/lib/types/store/migrate.d.ts +44 -0
- package/lib/types/store/paths.d.ts +32 -0
- package/lib/types/store/store.d.ts +133 -0
- package/lib/types/store.d.ts +7 -146
- package/package.json +28 -27
- package/src/client/SkillHubSettingsCard.tsx +17 -3
- package/src/client/api.ts +101 -195
- package/src/client/helpers.ts +10 -1
- package/src/client/locales/common.ts +102 -0
- package/src/client/locales/detail.ts +30 -0
- package/src/client/locales/market.ts +144 -0
- package/src/client/locales/settings.ts +80 -0
- package/src/client/locales/skills.ts +128 -0
- package/src/client/locales/sources.ts +68 -0
- package/src/client/locales.ts +26 -512
- package/src/client/panel/MarketSourceRow.tsx +108 -0
- package/src/client/panel/MarketView.tsx +12 -253
- package/src/client/panel/RepoScanCard.tsx +171 -0
- package/src/client/panel/hooks/shared.ts +81 -0
- package/src/client/panel/hooks/useCatalogFlow.ts +211 -0
- package/src/client/panel/hooks/useGroupFlow.ts +240 -0
- package/src/client/panel/hooks/useMarketFlow.ts +421 -0
- package/src/client/panel/hooks/useMetaFlow.ts +58 -0
- package/src/client/panel/hooks/useSourceFlow.ts +193 -0
- package/src/client/panel/market-format.ts +22 -0
- package/src/client/panel/useSkillHub.ts +141 -1032
- package/src/client/settings-card.tsx +51 -0
- package/src/client/settings-form.ts +18 -0
- package/src/index.ts +14 -4
- package/src/protocol/api.ts +38 -0
- package/src/protocol/catalog.ts +168 -0
- package/src/protocol/config.ts +145 -0
- package/src/protocol/groups.ts +106 -0
- package/src/protocol/market.ts +100 -0
- package/src/protocol/repo.ts +104 -0
- package/src/protocol/sources.ts +146 -0
- package/src/protocol/stats.ts +42 -0
- package/src/protocol.ts +83 -809
- package/src/repo/github-client.ts +131 -0
- package/src/repo.test.ts +24 -1
- package/src/repo.ts +50 -170
- package/src/routes/catalog.ts +309 -0
- package/src/routes/config.ts +119 -0
- package/src/routes/groups.ts +133 -0
- package/src/routes/helpers.ts +489 -0
- package/src/routes/market.ts +255 -0
- package/src/routes/repo-import.ts +267 -0
- package/src/routes/route-state.ts +89 -0
- package/src/routes/sources.ts +318 -0
- package/src/routes.test.ts +20 -0
- package/src/routes.ts +21 -1699
- package/src/stats.test.ts +58 -3
- package/src/stats.ts +45 -16
- package/src/store/errors.ts +14 -0
- package/src/store/migrate.ts +272 -0
- package/src/store/paths.ts +43 -0
- package/src/store/store.ts +532 -0
- package/src/store.test.ts +18 -0
- package/src/store.ts +7 -737
package/lib/client.js
CHANGED
|
@@ -6,13 +6,7 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
8
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
-
//#region src/protocol.ts
|
|
10
|
-
/**
|
|
11
|
-
* Shared API contract for dsh-skill-hub: the route paths and payload shapes
|
|
12
|
-
* both the host half and the browser half import. The browser half must never
|
|
13
|
-
* depend on host SDK packages, so registry types are re-spelled here as
|
|
14
|
-
* plain JSON-safe interfaces.
|
|
15
|
-
*/
|
|
9
|
+
//#region src/protocol/api.ts
|
|
16
10
|
/** Browser-facing base paths of the skill-hub API family. */
|
|
17
11
|
const SKILL_HUB_API = {
|
|
18
12
|
catalog: "/api/skill-hub/catalog",
|
|
@@ -48,8 +42,11 @@ window.__ModuleLoader__.load({
|
|
|
48
42
|
sourceRestore: "/api/skill-hub/sources/restore",
|
|
49
43
|
sourceTrashClear: "/api/skill-hub/sources/trash/clear",
|
|
50
44
|
diagnosticFix: "/api/skill-hub/diagnostic/fix",
|
|
51
|
-
marketSourceVersions: "/api/skill-hub/market/source/versions"
|
|
45
|
+
marketSourceVersions: "/api/skill-hub/market/source/versions",
|
|
46
|
+
marketStats: "/api/skill-hub/market/stats"
|
|
52
47
|
};
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/protocol/sources.ts
|
|
53
50
|
/** 项目级技能来源(它们有 workspace 归属,不属于「个人」组)。 */
|
|
54
51
|
function isProjectSource(source) {
|
|
55
52
|
return source === "project-dsh" || source === "project-agents";
|
|
@@ -97,270 +94,196 @@ window.__ModuleLoader__.load({
|
|
|
97
94
|
}
|
|
98
95
|
/** The browser half's only data entry point. */
|
|
99
96
|
var SkillHubApi = class {
|
|
97
|
+
/** One GET round trip (query already encoded by the caller). */
|
|
98
|
+
async get(path, query = "", ms) {
|
|
99
|
+
return readJson(await fetchWithTimeout(path + query, void 0, ms));
|
|
100
|
+
}
|
|
101
|
+
/** One POST round trip (body omitted for routes that take none). */
|
|
102
|
+
async post(path, body, ms) {
|
|
103
|
+
return readJson(await fetchWithTimeout(path, body !== void 0 ? {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers: { "content-type": "application/json" },
|
|
106
|
+
body: JSON.stringify(body)
|
|
107
|
+
} : { method: "POST" }, ms));
|
|
108
|
+
}
|
|
100
109
|
/** Catalog lookup options (cwd selects a workspace's project skills). */
|
|
101
110
|
catalog(options) {
|
|
102
|
-
|
|
111
|
+
const query = options?.cwd !== void 0 && options.cwd !== "" ? "?cwd=" + encodeURIComponent(options.cwd) : "";
|
|
112
|
+
return this.get(SKILL_HUB_API.catalog, query);
|
|
103
113
|
}
|
|
104
114
|
/** One skill's detail (cwd selects a workspace's project skills). */
|
|
105
115
|
async skill(name, options) {
|
|
106
116
|
const cwd = options?.cwd !== void 0 && options.cwd !== "" ? "&cwd=" + encodeURIComponent(options.cwd) : "";
|
|
107
|
-
return (await
|
|
117
|
+
return (await this.get(SKILL_HUB_API.skill, "?name=" + encodeURIComponent(name) + cwd)).skill;
|
|
108
118
|
}
|
|
109
119
|
/** Move one writable skill into the restorable trash. */
|
|
110
|
-
|
|
111
|
-
return
|
|
112
|
-
method: "POST",
|
|
113
|
-
headers: { "content-type": "application/json" },
|
|
114
|
-
body: JSON.stringify({ name })
|
|
115
|
-
}));
|
|
120
|
+
deleteSkill(name) {
|
|
121
|
+
return this.post(SKILL_HUB_API.skillDelete, { name });
|
|
116
122
|
}
|
|
117
123
|
/** Toggle one skill; resolves with the fresh catalog from the route. */
|
|
118
124
|
async toggle(name, enabled) {
|
|
119
|
-
return (await
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
name,
|
|
124
|
-
enabled
|
|
125
|
-
})
|
|
126
|
-
}))).catalog;
|
|
125
|
+
return (await this.post(SKILL_HUB_API.toggle, {
|
|
126
|
+
name,
|
|
127
|
+
enabled
|
|
128
|
+
})).catalog;
|
|
127
129
|
}
|
|
128
|
-
|
|
129
|
-
return
|
|
130
|
-
method: "POST",
|
|
131
|
-
headers: { "content-type": "application/json" },
|
|
132
|
-
body: JSON.stringify(payload)
|
|
133
|
-
}));
|
|
130
|
+
create(payload) {
|
|
131
|
+
return this.post(SKILL_HUB_API.create, payload);
|
|
134
132
|
}
|
|
135
|
-
|
|
136
|
-
return
|
|
133
|
+
stats() {
|
|
134
|
+
return this.get(SKILL_HUB_API.stats);
|
|
137
135
|
}
|
|
138
136
|
/** Toggle a whole group in one write; resolves with the fresh catalog + failures. */
|
|
139
|
-
|
|
137
|
+
toggleBatch(names, enabled) {
|
|
140
138
|
const payload = {
|
|
141
139
|
names,
|
|
142
140
|
enabled
|
|
143
141
|
};
|
|
144
|
-
return
|
|
145
|
-
method: "POST",
|
|
146
|
-
headers: { "content-type": "application/json" },
|
|
147
|
-
body: JSON.stringify(payload)
|
|
148
|
-
}));
|
|
142
|
+
return this.post(SKILL_HUB_API.toggleBatch, payload);
|
|
149
143
|
}
|
|
150
144
|
/** The user's added market sources. */
|
|
151
|
-
|
|
152
|
-
return
|
|
145
|
+
market() {
|
|
146
|
+
return this.get(SKILL_HUB_API.market);
|
|
153
147
|
}
|
|
154
148
|
/** Add a market source repo; resolves with the fresh list. */
|
|
155
|
-
|
|
156
|
-
return
|
|
157
|
-
method: "POST",
|
|
158
|
-
headers: { "content-type": "application/json" },
|
|
159
|
-
body: JSON.stringify({ repo })
|
|
160
|
-
}));
|
|
149
|
+
addMarketSource(repo) {
|
|
150
|
+
return this.post(SKILL_HUB_API.marketSource, { repo });
|
|
161
151
|
}
|
|
162
152
|
/** Remove a market source repo; resolves with the fresh list. */
|
|
163
|
-
|
|
164
|
-
return
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
153
|
+
removeMarketSource(repo) {
|
|
154
|
+
return this.post(SKILL_HUB_API.marketSourceDelete, { repo });
|
|
155
|
+
}
|
|
156
|
+
/** Stars + downloads per market source (hourly server-side cache; refresh=1 refetches stale entries). */
|
|
157
|
+
marketStats(refresh = false) {
|
|
158
|
+
const query = refresh ? "?refresh=1" : "";
|
|
159
|
+
return this.get(SKILL_HUB_API.marketStats, query, refresh ? 6e4 : void 0);
|
|
169
160
|
}
|
|
170
161
|
/** Check every market source for updates (throttled server-side). */
|
|
171
|
-
|
|
172
|
-
return
|
|
162
|
+
marketCheck() {
|
|
163
|
+
return this.get(SKILL_HUB_API.marketCheck);
|
|
173
164
|
}
|
|
174
165
|
/** Align a market source to its pinned ref; resolves with tracked skills. */
|
|
175
|
-
|
|
176
|
-
return
|
|
177
|
-
method: "POST",
|
|
178
|
-
headers: { "content-type": "application/json" },
|
|
179
|
-
body: JSON.stringify({ repo })
|
|
180
|
-
}));
|
|
166
|
+
marketSync(repo) {
|
|
167
|
+
return this.post(SKILL_HUB_API.marketSync, { repo });
|
|
181
168
|
}
|
|
182
169
|
/** Releases + branches of a market source for the version picker. */
|
|
183
|
-
|
|
184
|
-
return
|
|
170
|
+
marketSourceVersions(repo) {
|
|
171
|
+
return this.get(SKILL_HUB_API.marketSourceVersions, "?repo=" + encodeURIComponent(repo));
|
|
185
172
|
}
|
|
186
173
|
/** Pin a market source to an explicit ref (branch picker). */
|
|
187
|
-
|
|
188
|
-
return
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
repo,
|
|
193
|
-
ref
|
|
194
|
-
})
|
|
195
|
-
}));
|
|
174
|
+
setMarketSourceRef(repo, ref) {
|
|
175
|
+
return this.post(SKILL_HUB_API.marketSourceRef, {
|
|
176
|
+
repo,
|
|
177
|
+
ref
|
|
178
|
+
});
|
|
196
179
|
}
|
|
197
180
|
/** Check the hub's own latest GitHub release. */
|
|
198
|
-
|
|
199
|
-
return
|
|
181
|
+
updateCheck() {
|
|
182
|
+
return this.get(SKILL_HUB_API.update);
|
|
200
183
|
}
|
|
201
184
|
/** Discover importable skills in a GitHub repo. */
|
|
202
|
-
|
|
203
|
-
return
|
|
185
|
+
repoDiscover(repo) {
|
|
186
|
+
return this.get(SKILL_HUB_API.repo, "?repo=" + encodeURIComponent(repo));
|
|
204
187
|
}
|
|
205
188
|
/** Install selected repo skills into the user-dsh root (records the source). Returns jobId for polling. */
|
|
206
|
-
|
|
189
|
+
repoImport(repo, paths, ref) {
|
|
207
190
|
const payload = {
|
|
208
191
|
repo,
|
|
209
192
|
paths,
|
|
210
193
|
...ref !== void 0 && ref !== "" ? { ref } : {}
|
|
211
194
|
};
|
|
212
|
-
return
|
|
213
|
-
method: "POST",
|
|
214
|
-
headers: { "content-type": "application/json" },
|
|
215
|
-
body: JSON.stringify(payload)
|
|
216
|
-
}));
|
|
195
|
+
return this.post(SKILL_HUB_API.repoImport, payload);
|
|
217
196
|
}
|
|
218
197
|
/** Poll import job progress (B方案轮询) */
|
|
219
|
-
|
|
220
|
-
return
|
|
198
|
+
repoImportProgress(jobId) {
|
|
199
|
+
return this.get(SKILL_HUB_API.repoImportProgress, "?jobId=" + encodeURIComponent(jobId));
|
|
221
200
|
}
|
|
222
201
|
/** Cancel a running import job (选项2:唯有取消才停) */
|
|
223
|
-
|
|
224
|
-
return
|
|
225
|
-
method: "POST",
|
|
226
|
-
headers: { "content-type": "application/json" },
|
|
227
|
-
body: JSON.stringify({ jobId })
|
|
228
|
-
}));
|
|
202
|
+
repoImportCancel(jobId) {
|
|
203
|
+
return this.post(SKILL_HUB_API.repoImportCancel, { jobId });
|
|
229
204
|
}
|
|
230
205
|
/** Read the hub's runtime config (effective values + saved overrides). */
|
|
231
|
-
|
|
232
|
-
return
|
|
206
|
+
config() {
|
|
207
|
+
return this.get(SKILL_HUB_API.config);
|
|
233
208
|
}
|
|
234
209
|
/** Patch the hub's runtime config (null clears a saved override). */
|
|
235
|
-
|
|
236
|
-
return
|
|
237
|
-
method: "POST",
|
|
238
|
-
headers: { "content-type": "application/json" },
|
|
239
|
-
body: JSON.stringify(patch)
|
|
240
|
-
}));
|
|
210
|
+
saveConfig(patch) {
|
|
211
|
+
return this.post(SKILL_HUB_API.config, patch);
|
|
241
212
|
}
|
|
242
213
|
/** 用户 tag 分组 + 系统集合组 + origin 映射。 */
|
|
243
|
-
|
|
244
|
-
return
|
|
214
|
+
groups() {
|
|
215
|
+
return this.get(SKILL_HUB_API.groups);
|
|
245
216
|
}
|
|
246
217
|
/** 新建(无 id)或重命名(带 id)一个 tag 分组;返回全部 tag。 */
|
|
247
218
|
async saveTag(payload) {
|
|
248
|
-
return (await
|
|
249
|
-
method: "POST",
|
|
250
|
-
headers: { "content-type": "application/json" },
|
|
251
|
-
body: JSON.stringify(payload)
|
|
252
|
-
}))).tags;
|
|
219
|
+
return (await this.post(SKILL_HUB_API.tag, payload)).tags;
|
|
253
220
|
}
|
|
254
221
|
/** 删除一个 tag 分组;返回全部 tag。 */
|
|
255
222
|
async deleteTag(id) {
|
|
256
|
-
return (await
|
|
257
|
-
method: "POST",
|
|
258
|
-
headers: { "content-type": "application/json" },
|
|
259
|
-
body: JSON.stringify({ id })
|
|
260
|
-
}))).tags;
|
|
223
|
+
return (await this.post(SKILL_HUB_API.tagDelete, { id })).tags;
|
|
261
224
|
}
|
|
262
225
|
/** 直接设置某 tag 的完整成员列表;返回全部 tag。 */
|
|
263
226
|
async setTagMembers(id, skillNames) {
|
|
264
|
-
return (await
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
id,
|
|
269
|
-
skillNames
|
|
270
|
-
})
|
|
271
|
-
}))).tags;
|
|
227
|
+
return (await this.post(SKILL_HUB_API.tagMembers, {
|
|
228
|
+
id,
|
|
229
|
+
skillNames
|
|
230
|
+
})).tags;
|
|
272
231
|
}
|
|
273
232
|
/** 拖拽重排场景分组 */
|
|
274
233
|
async reorderTags(orderedIds) {
|
|
275
|
-
return (await
|
|
276
|
-
method: "POST",
|
|
277
|
-
headers: { "content-type": "application/json" },
|
|
278
|
-
body: JSON.stringify({ orderedIds })
|
|
279
|
-
}))).tags;
|
|
234
|
+
return (await this.post(SKILL_HUB_API.tagReorder, { orderedIds })).tags;
|
|
280
235
|
}
|
|
281
236
|
/** 拖拽重排来源集合 */
|
|
282
237
|
async reorderCollections(orderedNames) {
|
|
283
|
-
return (await
|
|
284
|
-
method: "POST",
|
|
285
|
-
headers: { "content-type": "application/json" },
|
|
286
|
-
body: JSON.stringify({ orderedNames })
|
|
287
|
-
}))).collections;
|
|
238
|
+
return (await this.post(SKILL_HUB_API.collectionReorder, { orderedNames })).collections;
|
|
288
239
|
}
|
|
289
240
|
/** 拖拽重排来源顶层分组(project / collections / personal) */
|
|
290
241
|
async reorderSourceGroups(orderedKeys) {
|
|
291
|
-
return (await
|
|
292
|
-
method: "POST",
|
|
293
|
-
headers: { "content-type": "application/json" },
|
|
294
|
-
body: JSON.stringify({ orderedKeys })
|
|
295
|
-
}))).order;
|
|
242
|
+
return (await this.post(SKILL_HUB_API.sourceGroupReorder, { orderedKeys })).order;
|
|
296
243
|
}
|
|
297
244
|
/** 来源列表 + 派生 origin 映射 + 集合组 + 回收站。 */
|
|
298
|
-
|
|
299
|
-
return
|
|
245
|
+
sources() {
|
|
246
|
+
return this.get(SKILL_HUB_API.sources);
|
|
300
247
|
}
|
|
301
248
|
/** 检查指定(或全部)来源的上游更新。 */
|
|
302
|
-
|
|
249
|
+
checkSources(repo) {
|
|
303
250
|
const payload = repo !== void 0 ? { repo } : {};
|
|
304
|
-
return
|
|
305
|
-
method: "POST",
|
|
306
|
-
headers: { "content-type": "application/json" },
|
|
307
|
-
body: JSON.stringify(payload)
|
|
308
|
-
}));
|
|
251
|
+
return this.post(SKILL_HUB_API.sourceCheck, payload);
|
|
309
252
|
}
|
|
310
253
|
/** 同步一个来源的所选(或全部)技能到上游最新版本。 */
|
|
311
|
-
|
|
254
|
+
syncSource(repo, skills) {
|
|
312
255
|
const payload = skills !== void 0 ? {
|
|
313
256
|
repo,
|
|
314
257
|
skills
|
|
315
258
|
} : { repo };
|
|
316
|
-
return
|
|
317
|
-
method: "POST",
|
|
318
|
-
headers: { "content-type": "application/json" },
|
|
319
|
-
body: JSON.stringify(payload)
|
|
320
|
-
}));
|
|
259
|
+
return this.post(SKILL_HUB_API.sourceSync, payload);
|
|
321
260
|
}
|
|
322
261
|
/** 跟进上游删除:把所选技能移入回收站。 */
|
|
323
|
-
|
|
262
|
+
confirmDeleteSource(repo, skills) {
|
|
324
263
|
const payload = {
|
|
325
264
|
repo,
|
|
326
265
|
skills
|
|
327
266
|
};
|
|
328
|
-
return
|
|
329
|
-
method: "POST",
|
|
330
|
-
headers: { "content-type": "application/json" },
|
|
331
|
-
body: JSON.stringify(payload)
|
|
332
|
-
}));
|
|
267
|
+
return this.post(SKILL_HUB_API.sourceDelete, payload);
|
|
333
268
|
}
|
|
334
269
|
/** 从回收站恢复一个技能。 */
|
|
335
|
-
|
|
270
|
+
restoreSource(name) {
|
|
336
271
|
const payload = { name };
|
|
337
|
-
return
|
|
338
|
-
method: "POST",
|
|
339
|
-
headers: { "content-type": "application/json" },
|
|
340
|
-
body: JSON.stringify(payload)
|
|
341
|
-
}));
|
|
272
|
+
return this.post(SKILL_HUB_API.sourceRestore, payload);
|
|
342
273
|
}
|
|
343
274
|
/** Permanently delete every skill currently in the trash. */
|
|
344
|
-
|
|
345
|
-
return
|
|
275
|
+
clearTrash() {
|
|
276
|
+
return this.post(SKILL_HUB_API.sourceTrashClear);
|
|
346
277
|
}
|
|
347
278
|
/** Auto-fix a fixable diagnostic (e.g. unquoted colon). */
|
|
348
|
-
|
|
349
|
-
return
|
|
350
|
-
method: "POST",
|
|
351
|
-
headers: { "content-type": "application/json" },
|
|
352
|
-
body: JSON.stringify({ path })
|
|
353
|
-
}));
|
|
279
|
+
fixDiagnostic(path) {
|
|
280
|
+
return this.post(SKILL_HUB_API.diagnosticFix, { path });
|
|
354
281
|
}
|
|
355
282
|
};
|
|
356
283
|
//#endregion
|
|
357
|
-
//#region src/client/locales.ts
|
|
358
|
-
/**
|
|
359
|
-
|
|
360
|
-
* so the LocaleNamespaceMap module augmentation in client/index.ts can
|
|
361
|
-
* reference it without a value import.
|
|
362
|
-
*/
|
|
363
|
-
const zh = {
|
|
284
|
+
//#region src/client/locales/common.ts
|
|
285
|
+
/** 面板框架与通用:入口/视图切换/面板/图例/报错/更新检查/新建表单/诊断修复。从 locales.ts 按前缀拆出,键集合不变。 */
|
|
286
|
+
const zhCommon = {
|
|
364
287
|
"entry.label": "技能",
|
|
365
288
|
"entry.tooltip": "技能中枢:浏览、分组与同步管理本地技能",
|
|
366
289
|
"view.sources": "来源",
|
|
@@ -386,6 +309,115 @@ window.__ModuleLoader__.load({
|
|
|
386
309
|
"panel.workspacePlaceholder": "工作区路径(回车应用)",
|
|
387
310
|
"panel.workspaceHint": "填写项目路径后,面板同时显示该项目 .dsh/skills 与 .agents/skills 下的技能(只读);留空回车恢复用户级视图。",
|
|
388
311
|
"panel.workspaceClear": "清除工作区",
|
|
312
|
+
"update.check": "检查更新",
|
|
313
|
+
"update.checking": "检查中…",
|
|
314
|
+
"update.upToDate": "已是最新版本 v{version}",
|
|
315
|
+
"update.available": "发现新版本 v{version}(当前 v{current})",
|
|
316
|
+
"update.newVersion": "新版本 v{version}",
|
|
317
|
+
"update.viewRelease": "查看发布",
|
|
318
|
+
"update.error": "检查更新失败:{error}",
|
|
319
|
+
"update.unavailable": "暂无可用的发布版本",
|
|
320
|
+
"diag.fix": "修复",
|
|
321
|
+
"diag.fixing": "修复中…",
|
|
322
|
+
"diag.fixed": "已修复",
|
|
323
|
+
"diag.fixConfirm": "用自动修复重写以下文件的 frontmatter?建议先备份:{path}",
|
|
324
|
+
"form.capabilityHint": "能力怎么选:重复方法 → 技能;外部系统 → MCP;长期资料 → 知识库。",
|
|
325
|
+
"form.descPlaceholder": "一句话说明用途(含触发词),Agent 靠它自动激活技能",
|
|
326
|
+
"form.name": "技能名(kebab-case,如 code-review)",
|
|
327
|
+
"form.desc": "描述(一句话路由说明)",
|
|
328
|
+
"form.root": "目标目录",
|
|
329
|
+
"form.submit": "创建",
|
|
330
|
+
"form.cancel": "取消",
|
|
331
|
+
"form.busy": "创建中…",
|
|
332
|
+
"form.success": "已创建:",
|
|
333
|
+
"form.error": "创建失败:",
|
|
334
|
+
"err.dismiss": "知道了"
|
|
335
|
+
};
|
|
336
|
+
const enCommon = {
|
|
337
|
+
"entry.label": "Skills",
|
|
338
|
+
"entry.tooltip": "Skill hub: browse, group, and sync local skills",
|
|
339
|
+
"view.sources": "Sources",
|
|
340
|
+
"view.scenes": "Scenes",
|
|
341
|
+
"view.market": "Market",
|
|
342
|
+
"view.flat": "Flat",
|
|
343
|
+
"view.grouped": "Grouped",
|
|
344
|
+
"panel.title": "Skill Hub",
|
|
345
|
+
"panel.count": "{count} skills total",
|
|
346
|
+
"panel.disabledCount": "{count} disabled",
|
|
347
|
+
"panel.new": "New Skill",
|
|
348
|
+
"panel.search": "Search skills…",
|
|
349
|
+
"legend.model": "Model-invocable — agents may call automatically",
|
|
350
|
+
"legend.user": "User-invocable — explicit invocation only",
|
|
351
|
+
"legend.hint": "At most one dot per skill; no dot = not invocable.",
|
|
352
|
+
"panel.enabled": "Enabled",
|
|
353
|
+
"panel.disabled": "Disabled",
|
|
354
|
+
"panel.diagnostics": "Discovery diagnostics",
|
|
355
|
+
"panel.empty": "No matching skills.",
|
|
356
|
+
"panel.emptyAll": "No skills installed yet. Create the first one.",
|
|
357
|
+
"panel.loading": "Loading…",
|
|
358
|
+
"panel.incomplete": "(catalog may be incomplete)",
|
|
359
|
+
"panel.workspacePlaceholder": "Workspace path (Enter to apply)",
|
|
360
|
+
"panel.workspaceHint": "Enter a project path to also show that workspace's .dsh/skills and .agents/skills skills (read-only); clear + Enter to return to the user-level view.",
|
|
361
|
+
"panel.workspaceClear": "Clear workspace",
|
|
362
|
+
"update.check": "Check for updates",
|
|
363
|
+
"update.checking": "Checking…",
|
|
364
|
+
"update.upToDate": "Up to date: v{version}",
|
|
365
|
+
"update.available": "Update available: v{version} (current v{current})",
|
|
366
|
+
"update.newVersion": "New version v{version}",
|
|
367
|
+
"update.viewRelease": "View release",
|
|
368
|
+
"update.error": "Update check failed: {error}",
|
|
369
|
+
"update.unavailable": "No release available",
|
|
370
|
+
"diag.fix": "Fix",
|
|
371
|
+
"diag.fixing": "Fixing…",
|
|
372
|
+
"diag.fixed": "Fixed",
|
|
373
|
+
"diag.fixConfirm": "Rewrite the frontmatter of this file with the auto-fix? Back it up first: {path}",
|
|
374
|
+
"form.capabilityHint": "Choosing a capability: repeatable method → skill; external system → MCP; long-term knowledge → knowledge base.",
|
|
375
|
+
"form.descPlaceholder": "One line on what it does (with trigger words) — agents auto-activate from this",
|
|
376
|
+
"form.name": "Skill name (kebab-case, e.g. code-review)",
|
|
377
|
+
"form.desc": "Description (one routing sentence)",
|
|
378
|
+
"form.root": "Target root",
|
|
379
|
+
"form.submit": "Create",
|
|
380
|
+
"form.cancel": "Cancel",
|
|
381
|
+
"form.busy": "Creating…",
|
|
382
|
+
"form.success": "Created: ",
|
|
383
|
+
"form.error": "Create failed: ",
|
|
384
|
+
"err.dismiss": "Dismiss"
|
|
385
|
+
};
|
|
386
|
+
//#endregion
|
|
387
|
+
//#region src/client/locales/detail.ts
|
|
388
|
+
/** 技能详情。从 locales.ts 按前缀拆出,键集合不变。 */
|
|
389
|
+
const zhDetail = {
|
|
390
|
+
"detail.copyMention": "复制 $mention",
|
|
391
|
+
"detail.copyPath": "复制路径",
|
|
392
|
+
"detail.copied": "已复制",
|
|
393
|
+
"detail.back": "返回列表",
|
|
394
|
+
"detail.provider": "提供者",
|
|
395
|
+
"detail.addedAt": "添加时间",
|
|
396
|
+
"detail.updatedAt": "更新时间",
|
|
397
|
+
"detail.path": "路径",
|
|
398
|
+
"detail.whenToUse": "适用场景",
|
|
399
|
+
"detail.uses": "调用次数",
|
|
400
|
+
"detail.groups": "所属场景",
|
|
401
|
+
"detail.loading": "读取技能正文…"
|
|
402
|
+
};
|
|
403
|
+
const enDetail = {
|
|
404
|
+
"detail.copyMention": "Copy $mention",
|
|
405
|
+
"detail.copyPath": "Copy path",
|
|
406
|
+
"detail.copied": "Copied",
|
|
407
|
+
"detail.back": "Back to list",
|
|
408
|
+
"detail.provider": "Provider",
|
|
409
|
+
"detail.addedAt": "Added",
|
|
410
|
+
"detail.updatedAt": "Updated",
|
|
411
|
+
"detail.path": "Path",
|
|
412
|
+
"detail.whenToUse": "When to use",
|
|
413
|
+
"detail.uses": "Uses",
|
|
414
|
+
"detail.groups": "Groups",
|
|
415
|
+
"detail.loading": "Loading skill body…"
|
|
416
|
+
};
|
|
417
|
+
//#endregion
|
|
418
|
+
//#region src/client/locales/market.ts
|
|
419
|
+
/** 市场与仓库扫描导入。从 locales.ts 按前缀拆出,键集合不变。 */
|
|
420
|
+
const zhMarket = {
|
|
389
421
|
"market.addPlaceholder": "owner/repo 或 https://github.com/…",
|
|
390
422
|
"market.addSource": "添加源",
|
|
391
423
|
"market.noSources": "还没有市场源。从下方内置市场添加,或输入一个 GitHub 仓库。",
|
|
@@ -430,6 +462,8 @@ window.__ModuleLoader__.load({
|
|
|
430
462
|
"market.versionConfirm": "切换到 {ref}",
|
|
431
463
|
"market.versionHint": "点击切换跟踪版本",
|
|
432
464
|
"market.unpinned": "未定版",
|
|
465
|
+
"market.starsHint": "GitHub 星标 {count} 个",
|
|
466
|
+
"market.downloadsHint": "发布附件累计下载 {count} 次",
|
|
433
467
|
"market.syncTitle": "同步本地技能",
|
|
434
468
|
"market.syncText": "市场源已对齐到 {ref},要同步更新以下本地技能吗?",
|
|
435
469
|
"market.syncNone": "该来源还没有导入本地技能。",
|
|
@@ -452,195 +486,9 @@ window.__ModuleLoader__.load({
|
|
|
452
486
|
"repo.importingCurrent": "正在导入: {name} ({done}/{total})",
|
|
453
487
|
"repo.root.skills": "技能 skills",
|
|
454
488
|
"repo.root.designTemplates": "模板 design-templates",
|
|
455
|
-
"repo.root.generic": "{root}"
|
|
456
|
-
"update.check": "检查更新",
|
|
457
|
-
"update.checking": "检查中…",
|
|
458
|
-
"update.upToDate": "已是最新版本 v{version}",
|
|
459
|
-
"update.available": "发现新版本 v{version}(当前 v{current})",
|
|
460
|
-
"update.newVersion": "新版本 v{version}",
|
|
461
|
-
"update.viewRelease": "查看发布",
|
|
462
|
-
"update.error": "检查更新失败:{error}",
|
|
463
|
-
"update.unavailable": "暂无可用的发布版本",
|
|
464
|
-
"group.project-dsh": "项目 .dsh/skills",
|
|
465
|
-
"group.project-agents": "项目 .agents/skills",
|
|
466
|
-
"group.custom": "自定义目录",
|
|
467
|
-
"group.runtime": "运行时",
|
|
468
|
-
"group.user-dsh": "用户 ~/.dsh/skills",
|
|
469
|
-
"group.user-agents": "用户 ~/.agents/skills",
|
|
470
|
-
"group.bundled": "内置",
|
|
471
|
-
"group.other": "其他来源",
|
|
472
|
-
"groups.new": "新建场景",
|
|
473
|
-
"groups.namePlaceholder": "场景名",
|
|
474
|
-
"groups.edit": "编辑",
|
|
475
|
-
"groups.rename": "保存名称",
|
|
476
|
-
"groups.delete": "删除场景",
|
|
477
|
-
"groups.saveMembers": "保存成员",
|
|
478
|
-
"groups.membersHint": "勾选技能加入此场景(含已禁用)。",
|
|
479
|
-
"groups.empty": "还没有场景。先新建一个,再把技能归类。",
|
|
480
|
-
"groups.noCollections": "暂无来源组。从市场导入技能后自动按来源聚合。",
|
|
481
|
-
"groups.personal": "个人",
|
|
482
|
-
"groups.project": "项目级",
|
|
483
|
-
"groups.subdivide": "细分",
|
|
484
|
-
"groups.merge": "合并",
|
|
485
|
-
"groups.noWritable": "该组没有可写技能(只读技能无法由中枢开关)",
|
|
486
|
-
"groups.closeAll": "全部关闭",
|
|
487
|
-
"groups.keepOn": "保留开启",
|
|
488
|
-
"groups.conflictTitle": "关闭此组?",
|
|
489
|
-
"groups.conflictText": "以下技能也在其他组中开启,关闭它们会影响那些组:",
|
|
490
|
-
"filter.allSources": "全部来源",
|
|
491
|
-
"filter.private": "个人",
|
|
492
|
-
"sort.name": "按名称",
|
|
493
|
-
"sort.added": "按添加时间",
|
|
494
|
-
"sort.uses": "按调用次数",
|
|
495
|
-
"time.justNow": "刚刚",
|
|
496
|
-
"time.minutesAgo": "{value} 分钟前",
|
|
497
|
-
"time.hoursAgo": "{value} 小时前",
|
|
498
|
-
"time.daysAgo": "{value} 天前",
|
|
499
|
-
"time.weeksAgo": "{value} 周前",
|
|
500
|
-
"badge.source.project-dsh": "项目 .dsh",
|
|
501
|
-
"badge.source.project-agents": "项目 .agents",
|
|
502
|
-
"badge.source.custom": "自定义",
|
|
503
|
-
"badge.source.runtime": "运行时",
|
|
504
|
-
"badge.source.user-dsh": "用户 .dsh",
|
|
505
|
-
"badge.source.user-agents": "用户 .agents",
|
|
506
|
-
"badge.source.bundled": "内置",
|
|
507
|
-
"row.readonly": "只读",
|
|
508
|
-
"row.disable": "禁用",
|
|
509
|
-
"row.enable": "启用",
|
|
510
|
-
"row.delete": "移入回收站",
|
|
511
|
-
"row.open": "查看 {name} 详情(回车)",
|
|
512
|
-
"row.duplicate": "重名",
|
|
513
|
-
"row.duplicateHint": "同名技能存在多个来源,当前显示为优先级最高的版本",
|
|
514
|
-
"row.copy": "复制",
|
|
515
|
-
"row.copyMention": "复制 $mention",
|
|
516
|
-
"row.copied": "已复制",
|
|
517
|
-
"row.copyPath": "复制路径",
|
|
518
|
-
"detail.copyMention": "复制 $mention",
|
|
519
|
-
"detail.copyPath": "复制路径",
|
|
520
|
-
"detail.copied": "已复制",
|
|
521
|
-
"diag.fix": "修复",
|
|
522
|
-
"diag.fixing": "修复中…",
|
|
523
|
-
"diag.fixed": "已修复",
|
|
524
|
-
"diag.fixConfirm": "用自动修复重写以下文件的 frontmatter?建议先备份:{path}",
|
|
525
|
-
"filter.invocation": "调用",
|
|
526
|
-
"filter.invocationAll": "全部调用",
|
|
527
|
-
"filter.modelOnly": "模型可调",
|
|
528
|
-
"filter.userOnly": "用户可调",
|
|
529
|
-
"filter.showing": "显示 {shown}/{total}(已启用){filtered}",
|
|
530
|
-
"filter.filteredSuffix": " · 已筛选",
|
|
531
|
-
"filter.shortened": "短描述 {count} 条,悬浮看全文",
|
|
532
|
-
"delete.confirmTitle": "移入回收站?",
|
|
533
|
-
"delete.confirmText": "确定把「{name}」移入回收站吗?可随时恢复。",
|
|
534
|
-
"delete.confirm": "移入回收站",
|
|
535
|
-
"detail.back": "返回列表",
|
|
536
|
-
"detail.provider": "提供者",
|
|
537
|
-
"detail.addedAt": "添加时间",
|
|
538
|
-
"detail.updatedAt": "更新时间",
|
|
539
|
-
"detail.path": "路径",
|
|
540
|
-
"detail.whenToUse": "适用场景",
|
|
541
|
-
"detail.uses": "调用次数",
|
|
542
|
-
"detail.groups": "所属场景",
|
|
543
|
-
"detail.loading": "读取技能正文…",
|
|
544
|
-
"source.private": "个人技能(未绑定上游,不检查更新)",
|
|
545
|
-
"source.commit": "已同步版本",
|
|
546
|
-
"source.unverified": "尚未核对上游",
|
|
547
|
-
"source.check": "检查更新",
|
|
548
|
-
"source.checkHint": "点击检查上游更新",
|
|
549
|
-
"source.checking": "检查中…",
|
|
550
|
-
"source.upToDate": "已是最新",
|
|
551
|
-
"source.updated": "有更新 {count} 个",
|
|
552
|
-
"source.deleted": "上游已删除 {count} 个",
|
|
553
|
-
"source.error": "检查失败",
|
|
554
|
-
"source.throttled": "刚刚检查过",
|
|
555
|
-
"source.sync": "同步",
|
|
556
|
-
"source.syncing": "同步中…",
|
|
557
|
-
"source.syncConfirmTitle": "同步技能?",
|
|
558
|
-
"source.syncConfirmText": "将用上游最新版本覆盖本地对以下技能的所有修改:",
|
|
559
|
-
"source.followDelete": "跟进删除",
|
|
560
|
-
"source.deleteConfirmTitle": "跟进上游删除?",
|
|
561
|
-
"source.deleteConfirmText": "以下技能已从上游仓库移除,本地目录将移入回收站(可恢复):",
|
|
562
|
-
"source.deleteGroup": "删除整组",
|
|
563
|
-
"source.deleteGroupHint": "删除该分组下的全部 {count} 个技能",
|
|
564
|
-
"source.deleteGroupTitle": "删除整组?",
|
|
565
|
-
"source.deleteGroupText": "将把分组“{name}”下的全部 {count} 个技能移入回收站(可恢复),只读技能会跳过:",
|
|
566
|
-
"source.trash": "回收站",
|
|
567
|
-
"source.restore": "恢复",
|
|
568
|
-
"source.clearTrash": "清空回收站",
|
|
569
|
-
"source.clearTrashConfirmTitle": "清空回收站?",
|
|
570
|
-
"source.clearTrashConfirmText": "回收站里的技能会被永久删除,无法恢复。此操作不可撤销。",
|
|
571
|
-
"source.clearTrashConfirm": "永久删除",
|
|
572
|
-
"form.capabilityHint": "能力怎么选:重复方法 → 技能;外部系统 → MCP;长期资料 → 知识库。",
|
|
573
|
-
"form.descPlaceholder": "一句话说明用途(含触发词),Agent 靠它自动激活技能",
|
|
574
|
-
"form.name": "技能名(kebab-case,如 code-review)",
|
|
575
|
-
"form.desc": "描述(一句话路由说明)",
|
|
576
|
-
"form.root": "目标目录",
|
|
577
|
-
"form.submit": "创建",
|
|
578
|
-
"form.cancel": "取消",
|
|
579
|
-
"form.busy": "创建中…",
|
|
580
|
-
"form.success": "已创建:",
|
|
581
|
-
"form.error": "创建失败:",
|
|
582
|
-
"err.dismiss": "知道了",
|
|
583
|
-
"settings.title": "技能中枢",
|
|
584
|
-
"settings.description": "管理 dsh 本地技能目录:浏览、分组、来源同步与新建。",
|
|
585
|
-
"settings.enabled": "启用插件",
|
|
586
|
-
"settings.enabledHint": "关闭后技能中枢的路由、入口与公告全部下线。",
|
|
587
|
-
"settings.announceToAgent": "向 Agent 公告",
|
|
588
|
-
"settings.announceToAgentHint": "在系统提示中加入本插件说明,用户提到技能管理时 Agent 知道如何协作。",
|
|
589
|
-
"settings.dotModelColor": "模型可调圆点颜色",
|
|
590
|
-
"settings.dotModelColorHint": "技能行/聊天 / 菜单中「模型可调」蓝色圆点的颜色(#rrggbb)。",
|
|
591
|
-
"settings.dotUserColor": "用户可调圆点颜色",
|
|
592
|
-
"settings.dotUserColorHint": "技能行/聊天 / 菜单中「仅用户可调」绿色圆点的颜色(#rrggbb)。",
|
|
593
|
-
"settings.showUseCount": "显示调用次数",
|
|
594
|
-
"settings.showUseCountHint": "在技能名旁显示琥珀色调用次数。",
|
|
595
|
-
"settings.showUseTime": "显示最近调用时间",
|
|
596
|
-
"settings.showUseTimeHint": "在技能名行右侧显示相对时间(如「3 天前」)。",
|
|
597
|
-
"settings.showGroupSummary": "显示分组汇总",
|
|
598
|
-
"settings.showGroupSummaryHint": "在分组标题后汇总调用次数与最近调用时间。",
|
|
599
|
-
"settings.statsWindowDays": "统计窗口(天)",
|
|
600
|
-
"settings.statsWindowDaysHint": "只统计最近 N 天的使用次数,默认 14 天;0 = 全部历史。改动立即生效。",
|
|
601
|
-
"settings.statsScanMinutes": "自动统计间隔(分钟)",
|
|
602
|
-
"settings.statsScanMinutesHint": "后台扫描会话日志的间隔,最小 1 分钟;扫描耗时会自动拉长间隔。",
|
|
603
|
-
"settings.inherit": "继承",
|
|
604
|
-
"settings.on": "开",
|
|
605
|
-
"settings.off": "关",
|
|
606
|
-
"settings.overridden": "已覆盖",
|
|
607
|
-
"settings.reset": "恢复默认",
|
|
608
|
-
"settings.unsaved": "未保存",
|
|
609
|
-
"settings.save": "保存",
|
|
610
|
-
"settings.saving": "保存中…",
|
|
611
|
-
"settings.discard": "放弃修改",
|
|
612
|
-
"settings.saveFailed": "本部署没有接受这些值,已保留供你修改。",
|
|
613
|
-
"settings.readOnly": "本部署的设置为只读。",
|
|
614
|
-
"settings.notExposed": "本部署没有挂载该设置命名空间。",
|
|
615
|
-
"settings.collapse": "收起设置",
|
|
616
|
-
"settings.expand": "展开设置"
|
|
489
|
+
"repo.root.generic": "{root}"
|
|
617
490
|
};
|
|
618
|
-
const
|
|
619
|
-
"entry.label": "Skills",
|
|
620
|
-
"entry.tooltip": "Skill hub: browse, group, and sync local skills",
|
|
621
|
-
"view.sources": "Sources",
|
|
622
|
-
"view.scenes": "Scenes",
|
|
623
|
-
"view.market": "Market",
|
|
624
|
-
"view.flat": "Flat",
|
|
625
|
-
"view.grouped": "Grouped",
|
|
626
|
-
"panel.title": "Skill Hub",
|
|
627
|
-
"panel.count": "{count} skills total",
|
|
628
|
-
"panel.disabledCount": "{count} disabled",
|
|
629
|
-
"panel.new": "New Skill",
|
|
630
|
-
"panel.search": "Search skills…",
|
|
631
|
-
"legend.model": "Model-invocable — agents may call automatically",
|
|
632
|
-
"legend.user": "User-invocable — explicit invocation only",
|
|
633
|
-
"legend.hint": "At most one dot per skill; no dot = not invocable.",
|
|
634
|
-
"panel.enabled": "Enabled",
|
|
635
|
-
"panel.disabled": "Disabled",
|
|
636
|
-
"panel.diagnostics": "Discovery diagnostics",
|
|
637
|
-
"panel.empty": "No matching skills.",
|
|
638
|
-
"panel.emptyAll": "No skills installed yet. Create the first one.",
|
|
639
|
-
"panel.loading": "Loading…",
|
|
640
|
-
"panel.incomplete": "(catalog may be incomplete)",
|
|
641
|
-
"panel.workspacePlaceholder": "Workspace path (Enter to apply)",
|
|
642
|
-
"panel.workspaceHint": "Enter a project path to also show that workspace's .dsh/skills and .agents/skills skills (read-only); clear + Enter to return to the user-level view.",
|
|
643
|
-
"panel.workspaceClear": "Clear workspace",
|
|
491
|
+
const enMarket = {
|
|
644
492
|
"market.addPlaceholder": "owner/repo or https://github.com/…",
|
|
645
493
|
"market.addSource": "Add source",
|
|
646
494
|
"market.noSources": "No market sources yet. Add one from the built-in catalog below, or enter a GitHub repo.",
|
|
@@ -685,6 +533,8 @@ window.__ModuleLoader__.load({
|
|
|
685
533
|
"market.versionConfirm": "Switch to {ref}",
|
|
686
534
|
"market.versionHint": "Click to switch the tracked version",
|
|
687
535
|
"market.unpinned": "unpinned",
|
|
536
|
+
"market.starsHint": "{count} GitHub stars",
|
|
537
|
+
"market.downloadsHint": "{count} release asset downloads",
|
|
688
538
|
"market.syncTitle": "Update local skills",
|
|
689
539
|
"market.syncText": "Market source aligned to {ref}. Update these local skills?",
|
|
690
540
|
"market.syncNone": "No local skills tracked from this repo yet.",
|
|
@@ -707,15 +557,156 @@ window.__ModuleLoader__.load({
|
|
|
707
557
|
"repo.importingCurrent": "Importing: {name} ({done}/{total})",
|
|
708
558
|
"repo.root.skills": "Skills",
|
|
709
559
|
"repo.root.designTemplates": "Templates design-templates",
|
|
710
|
-
"repo.root.generic": "{root}"
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
"
|
|
717
|
-
"
|
|
718
|
-
"
|
|
560
|
+
"repo.root.generic": "{root}"
|
|
561
|
+
};
|
|
562
|
+
//#endregion
|
|
563
|
+
//#region src/client/locales/settings.ts
|
|
564
|
+
/** 设置卡。从 locales.ts 按前缀拆出,键集合不变。 */
|
|
565
|
+
const zhSettings = {
|
|
566
|
+
"settings.title": "技能中枢",
|
|
567
|
+
"settings.description": "管理 dsh 本地技能目录:浏览、分组、来源同步与新建。",
|
|
568
|
+
"settings.enabled": "启用插件",
|
|
569
|
+
"settings.enabledHint": "关闭后技能中枢的路由、入口与公告全部下线。",
|
|
570
|
+
"settings.announceToAgent": "向 Agent 公告",
|
|
571
|
+
"settings.announceToAgentHint": "在系统提示中加入本插件说明,用户提到技能管理时 Agent 知道如何协作。",
|
|
572
|
+
"settings.dotModelColor": "模型可调圆点颜色",
|
|
573
|
+
"settings.dotModelColorHint": "技能行/聊天 / 菜单中「模型可调」蓝色圆点的颜色(#rrggbb)。",
|
|
574
|
+
"settings.dotUserColor": "用户可调圆点颜色",
|
|
575
|
+
"settings.dotUserColorHint": "技能行/聊天 / 菜单中「仅用户可调」绿色圆点的颜色(#rrggbb)。",
|
|
576
|
+
"settings.showUseCount": "显示调用次数",
|
|
577
|
+
"settings.showUseCountHint": "在技能名旁显示琥珀色调用次数。",
|
|
578
|
+
"settings.showUseTime": "显示最近调用时间",
|
|
579
|
+
"settings.showUseTimeHint": "在技能名行右侧显示相对时间(如「3 天前」)。",
|
|
580
|
+
"settings.showGroupSummary": "显示分组汇总",
|
|
581
|
+
"settings.showGroupSummaryHint": "在分组标题后汇总调用次数与最近调用时间。",
|
|
582
|
+
"settings.statsWindowDays": "统计窗口(天)",
|
|
583
|
+
"settings.statsWindowDaysHint": "只统计最近 N 天的使用次数,默认 14 天;0 = 全部历史。改动立即生效。",
|
|
584
|
+
"settings.statsScanMinutes": "自动统计间隔(分钟)",
|
|
585
|
+
"settings.statsScanMinutesHint": "后台扫描会话日志的间隔,最小 1 分钟;扫描耗时会自动拉长间隔。",
|
|
586
|
+
"settings.githubToken": "GitHub Token",
|
|
587
|
+
"settings.githubTokenHint": "市场/来源走 GitHub API,匿名每小时 60 次,填 token 后 5000 次。细粒度 token 无需任何权限。明文存本地设置,仅回环可读;留空即匿名(或跟随 GITHUB_TOKEN 环境变量)。",
|
|
588
|
+
"settings.githubTokenPlaceholder": "ghp_… / github_pat_…(留空为匿名)",
|
|
589
|
+
"settings.inherit": "继承",
|
|
590
|
+
"settings.on": "开",
|
|
591
|
+
"settings.off": "关",
|
|
592
|
+
"settings.overridden": "已覆盖",
|
|
593
|
+
"settings.reset": "恢复默认",
|
|
594
|
+
"settings.unsaved": "未保存",
|
|
595
|
+
"settings.save": "保存",
|
|
596
|
+
"settings.saving": "保存中…",
|
|
597
|
+
"settings.discard": "放弃修改",
|
|
598
|
+
"settings.saveFailed": "本部署没有接受这些值,已保留供你修改。",
|
|
599
|
+
"settings.readOnly": "本部署的设置为只读。",
|
|
600
|
+
"settings.notExposed": "本部署没有挂载该设置命名空间。",
|
|
601
|
+
"settings.collapse": "收起设置",
|
|
602
|
+
"settings.expand": "展开设置"
|
|
603
|
+
};
|
|
604
|
+
const enSettings = {
|
|
605
|
+
"settings.title": "Skill Hub",
|
|
606
|
+
"settings.description": "Manage the local dsh skill catalog: browse, group, sync, and scaffold skills.",
|
|
607
|
+
"settings.enabled": "Enable plugin",
|
|
608
|
+
"settings.enabledHint": "Disables the hub routes, entries, and announcement.",
|
|
609
|
+
"settings.announceToAgent": "Announce to agent",
|
|
610
|
+
"settings.announceToAgentHint": "Adds a system-prompt section so agents know how to collaborate when users mention skill management.",
|
|
611
|
+
"settings.dotModelColor": "Model-invocable dot color",
|
|
612
|
+
"settings.dotModelColorHint": "Color of the blue \"model-callable\" dot in the skill panel and the chat / menu (#rrggbb).",
|
|
613
|
+
"settings.dotUserColor": "User-invocable dot color",
|
|
614
|
+
"settings.dotUserColorHint": "Color of the green \"user-only\" dot in the skill panel and the chat / menu (#rrggbb).",
|
|
615
|
+
"settings.showUseCount": "Show invocation count",
|
|
616
|
+
"settings.showUseCountHint": "Show the amber invocation count beside each skill name.",
|
|
617
|
+
"settings.showUseTime": "Show last-used time",
|
|
618
|
+
"settings.showUseTimeHint": "Show relative last-used time on the name row (e.g. \"3 d ago\").",
|
|
619
|
+
"settings.showGroupSummary": "Show group summaries",
|
|
620
|
+
"settings.showGroupSummaryHint": "Summarize invocation count and last-used time after group titles.",
|
|
621
|
+
"settings.statsWindowDays": "Stats window (days)",
|
|
622
|
+
"settings.statsWindowDaysHint": "Count only usage within the last N days (default 14); 0 = full history. Changes apply immediately.",
|
|
623
|
+
"settings.statsScanMinutes": "Auto stats interval (minutes)",
|
|
624
|
+
"settings.statsScanMinutesHint": "How often the background session-log scan runs (min 1); heavy scans automatically stretch it.",
|
|
625
|
+
"settings.githubToken": "GitHub token",
|
|
626
|
+
"settings.githubTokenHint": "Market/sources use the GitHub API: 60 req/h anonymous, 5000/hr with a token. A fine-grained token needs no permissions. Stored plaintext in local settings, loopback-only; empty means anonymous (or GITHUB_TOKEN env).",
|
|
627
|
+
"settings.githubTokenPlaceholder": "ghp_… / github_pat_… (empty = anonymous)",
|
|
628
|
+
"settings.inherit": "Inherit",
|
|
629
|
+
"settings.on": "On",
|
|
630
|
+
"settings.off": "Off",
|
|
631
|
+
"settings.overridden": "Overridden",
|
|
632
|
+
"settings.reset": "Reset to default",
|
|
633
|
+
"settings.unsaved": "Unsaved",
|
|
634
|
+
"settings.save": "Save",
|
|
635
|
+
"settings.saving": "Saving…",
|
|
636
|
+
"settings.discard": "Discard",
|
|
637
|
+
"settings.saveFailed": "The deployment did not accept these values; they were left for you to correct.",
|
|
638
|
+
"settings.readOnly": "This deployment stores settings read-only.",
|
|
639
|
+
"settings.notExposed": "This deployment does not expose the settings namespace.",
|
|
640
|
+
"settings.collapse": "Hide settings",
|
|
641
|
+
"settings.expand": "Show settings"
|
|
642
|
+
};
|
|
643
|
+
//#endregion
|
|
644
|
+
//#region src/client/locales/skills.ts
|
|
645
|
+
/** 技能浏览:行徽章/分组/场景/过滤排序/相对时间。从 locales.ts 按前缀拆出,键集合不变。 */
|
|
646
|
+
const zhSkills = {
|
|
647
|
+
"group.project-dsh": "项目 .dsh/skills",
|
|
648
|
+
"group.project-agents": "项目 .agents/skills",
|
|
649
|
+
"group.custom": "自定义目录",
|
|
650
|
+
"group.runtime": "运行时",
|
|
651
|
+
"group.user-dsh": "用户 ~/.dsh/skills",
|
|
652
|
+
"group.user-agents": "用户 ~/.agents/skills",
|
|
653
|
+
"group.bundled": "内置",
|
|
654
|
+
"group.other": "其他来源",
|
|
655
|
+
"groups.new": "新建场景",
|
|
656
|
+
"groups.namePlaceholder": "场景名",
|
|
657
|
+
"groups.edit": "编辑",
|
|
658
|
+
"groups.rename": "保存名称",
|
|
659
|
+
"groups.delete": "删除场景",
|
|
660
|
+
"groups.saveMembers": "保存成员",
|
|
661
|
+
"groups.membersHint": "勾选技能加入此场景(含已禁用)。",
|
|
662
|
+
"groups.empty": "还没有场景。先新建一个,再把技能归类。",
|
|
663
|
+
"groups.noCollections": "暂无来源组。从市场导入技能后自动按来源聚合。",
|
|
664
|
+
"groups.personal": "个人",
|
|
665
|
+
"groups.project": "项目级",
|
|
666
|
+
"groups.subdivide": "细分",
|
|
667
|
+
"groups.merge": "合并",
|
|
668
|
+
"groups.noWritable": "该组没有可写技能(只读技能无法由中枢开关)",
|
|
669
|
+
"groups.closeAll": "全部关闭",
|
|
670
|
+
"groups.keepOn": "保留开启",
|
|
671
|
+
"groups.conflictTitle": "关闭此组?",
|
|
672
|
+
"groups.conflictText": "以下技能也在其他组中开启,关闭它们会影响那些组:",
|
|
673
|
+
"filter.allSources": "全部来源",
|
|
674
|
+
"filter.private": "个人",
|
|
675
|
+
"sort.name": "按名称",
|
|
676
|
+
"sort.added": "按添加时间",
|
|
677
|
+
"sort.uses": "按调用次数",
|
|
678
|
+
"time.justNow": "刚刚",
|
|
679
|
+
"time.minutesAgo": "{value} 分钟前",
|
|
680
|
+
"time.hoursAgo": "{value} 小时前",
|
|
681
|
+
"time.daysAgo": "{value} 天前",
|
|
682
|
+
"time.weeksAgo": "{value} 周前",
|
|
683
|
+
"badge.source.project-dsh": "项目 .dsh",
|
|
684
|
+
"badge.source.project-agents": "项目 .agents",
|
|
685
|
+
"badge.source.custom": "自定义",
|
|
686
|
+
"badge.source.runtime": "运行时",
|
|
687
|
+
"badge.source.user-dsh": "用户 .dsh",
|
|
688
|
+
"badge.source.user-agents": "用户 .agents",
|
|
689
|
+
"badge.source.bundled": "内置",
|
|
690
|
+
"row.readonly": "只读",
|
|
691
|
+
"row.disable": "禁用",
|
|
692
|
+
"row.enable": "启用",
|
|
693
|
+
"row.delete": "移入回收站",
|
|
694
|
+
"row.open": "查看 {name} 详情(回车)",
|
|
695
|
+
"row.duplicate": "重名",
|
|
696
|
+
"row.duplicateHint": "同名技能存在多个来源,当前显示为优先级最高的版本",
|
|
697
|
+
"row.copy": "复制",
|
|
698
|
+
"row.copyMention": "复制 $mention",
|
|
699
|
+
"row.copied": "已复制",
|
|
700
|
+
"row.copyPath": "复制路径",
|
|
701
|
+
"filter.invocation": "调用",
|
|
702
|
+
"filter.invocationAll": "全部调用",
|
|
703
|
+
"filter.modelOnly": "模型可调",
|
|
704
|
+
"filter.userOnly": "用户可调",
|
|
705
|
+
"filter.showing": "显示 {shown}/{total}(已启用){filtered}",
|
|
706
|
+
"filter.filteredSuffix": " · 已筛选",
|
|
707
|
+
"filter.shortened": "短描述 {count} 条,悬浮看全文"
|
|
708
|
+
};
|
|
709
|
+
const enSkills = {
|
|
719
710
|
"group.project-dsh": "Project .dsh/skills",
|
|
720
711
|
"group.project-agents": "Project .agents/skills",
|
|
721
712
|
"group.custom": "Custom directories",
|
|
@@ -770,32 +761,54 @@ window.__ModuleLoader__.load({
|
|
|
770
761
|
"row.copyMention": "Copy $mention",
|
|
771
762
|
"row.copied": "Copied",
|
|
772
763
|
"row.copyPath": "Copy path",
|
|
773
|
-
"detail.copyMention": "Copy $mention",
|
|
774
|
-
"detail.copyPath": "Copy path",
|
|
775
|
-
"detail.copied": "Copied",
|
|
776
|
-
"diag.fix": "Fix",
|
|
777
|
-
"diag.fixing": "Fixing…",
|
|
778
|
-
"diag.fixed": "Fixed",
|
|
779
|
-
"diag.fixConfirm": "Rewrite the frontmatter of this file with the auto-fix? Back it up first: {path}",
|
|
780
764
|
"filter.invocation": "Invocation",
|
|
781
765
|
"filter.invocationAll": "All",
|
|
782
766
|
"filter.modelOnly": "Model",
|
|
783
767
|
"filter.userOnly": "User",
|
|
784
768
|
"filter.showing": "Showing {shown}/{total} (enabled){filtered}",
|
|
785
769
|
"filter.filteredSuffix": " · filtered",
|
|
786
|
-
"filter.shortened": "{count} short descriptions, hover for full text"
|
|
787
|
-
|
|
770
|
+
"filter.shortened": "{count} short descriptions, hover for full text"
|
|
771
|
+
};
|
|
772
|
+
//#endregion
|
|
773
|
+
//#region src/client/locales/sources.ts
|
|
774
|
+
/** 来源跟踪与回收站(含删除确认)。从 locales.ts 按前缀拆出,键集合不变。 */
|
|
775
|
+
const zhSources = {
|
|
776
|
+
"delete.confirmTitle": "移入回收站?",
|
|
777
|
+
"delete.confirmText": "确定把「{name}」移入回收站吗?可随时恢复。",
|
|
778
|
+
"delete.confirm": "移入回收站",
|
|
779
|
+
"source.private": "个人技能(未绑定上游,不检查更新)",
|
|
780
|
+
"source.commit": "已同步版本",
|
|
781
|
+
"source.unverified": "尚未核对上游",
|
|
782
|
+
"source.check": "检查更新",
|
|
783
|
+
"source.checkHint": "点击检查上游更新",
|
|
784
|
+
"source.checking": "检查中…",
|
|
785
|
+
"source.upToDate": "已是最新",
|
|
786
|
+
"source.updated": "有更新 {count} 个",
|
|
787
|
+
"source.deleted": "上游已删除 {count} 个",
|
|
788
|
+
"source.error": "检查失败",
|
|
789
|
+
"source.throttled": "刚刚检查过",
|
|
790
|
+
"source.sync": "同步",
|
|
791
|
+
"source.syncing": "同步中…",
|
|
792
|
+
"source.syncConfirmTitle": "同步技能?",
|
|
793
|
+
"source.syncConfirmText": "将用上游最新版本覆盖本地对以下技能的所有修改:",
|
|
794
|
+
"source.followDelete": "跟进删除",
|
|
795
|
+
"source.deleteConfirmTitle": "跟进上游删除?",
|
|
796
|
+
"source.deleteConfirmText": "以下技能已从上游仓库移除,本地目录将移入回收站(可恢复):",
|
|
797
|
+
"source.deleteGroup": "删除整组",
|
|
798
|
+
"source.deleteGroupHint": "删除该分组下的全部 {count} 个技能",
|
|
799
|
+
"source.deleteGroupTitle": "删除整组?",
|
|
800
|
+
"source.deleteGroupText": "将把分组“{name}”下的全部 {count} 个技能移入回收站(可恢复),只读技能会跳过:",
|
|
801
|
+
"source.trash": "回收站",
|
|
802
|
+
"source.restore": "恢复",
|
|
803
|
+
"source.clearTrash": "清空回收站",
|
|
804
|
+
"source.clearTrashConfirmTitle": "清空回收站?",
|
|
805
|
+
"source.clearTrashConfirmText": "回收站里的技能会被永久删除,无法恢复。此操作不可撤销。",
|
|
806
|
+
"source.clearTrashConfirm": "永久删除"
|
|
807
|
+
};
|
|
808
|
+
const enSources = {
|
|
809
|
+
"delete.confirmTitle": "Move to trash?",
|
|
788
810
|
"delete.confirmText": "Move \"{name}\" to trash? It can be restored anytime.",
|
|
789
811
|
"delete.confirm": "Move to trash",
|
|
790
|
-
"detail.back": "Back to list",
|
|
791
|
-
"detail.provider": "Provider",
|
|
792
|
-
"detail.addedAt": "Added",
|
|
793
|
-
"detail.updatedAt": "Updated",
|
|
794
|
-
"detail.path": "Path",
|
|
795
|
-
"detail.whenToUse": "When to use",
|
|
796
|
-
"detail.uses": "Uses",
|
|
797
|
-
"detail.groups": "Groups",
|
|
798
|
-
"detail.loading": "Loading skill body…",
|
|
799
812
|
"source.private": "Personal skill (not bound to an upstream; no update checks)",
|
|
800
813
|
"source.commit": "Synced commit",
|
|
801
814
|
"source.unverified": "upstream not verified yet",
|
|
@@ -823,52 +836,31 @@ window.__ModuleLoader__.load({
|
|
|
823
836
|
"source.clearTrash": "Empty trash",
|
|
824
837
|
"source.clearTrashConfirmTitle": "Empty the trash?",
|
|
825
838
|
"source.clearTrashConfirmText": "Skills in the trash will be permanently deleted and cannot be restored.",
|
|
826
|
-
"source.clearTrashConfirm": "Delete permanently"
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
"settings.showUseTimeHint": "Show relative last-used time on the name row (e.g. \"3 d ago\").",
|
|
852
|
-
"settings.showGroupSummary": "Show group summaries",
|
|
853
|
-
"settings.showGroupSummaryHint": "Summarize invocation count and last-used time after group titles.",
|
|
854
|
-
"settings.statsWindowDays": "Stats window (days)",
|
|
855
|
-
"settings.statsWindowDaysHint": "Count only usage within the last N days (default 14); 0 = full history. Changes apply immediately.",
|
|
856
|
-
"settings.statsScanMinutes": "Auto stats interval (minutes)",
|
|
857
|
-
"settings.statsScanMinutesHint": "How often the background session-log scan runs (min 1); heavy scans automatically stretch it.",
|
|
858
|
-
"settings.inherit": "Inherit",
|
|
859
|
-
"settings.on": "On",
|
|
860
|
-
"settings.off": "Off",
|
|
861
|
-
"settings.overridden": "Overridden",
|
|
862
|
-
"settings.reset": "Reset to default",
|
|
863
|
-
"settings.unsaved": "Unsaved",
|
|
864
|
-
"settings.save": "Save",
|
|
865
|
-
"settings.saving": "Saving…",
|
|
866
|
-
"settings.discard": "Discard",
|
|
867
|
-
"settings.saveFailed": "The deployment did not accept these values; they were left for you to correct.",
|
|
868
|
-
"settings.readOnly": "This deployment stores settings read-only.",
|
|
869
|
-
"settings.notExposed": "This deployment does not expose the settings namespace.",
|
|
870
|
-
"settings.collapse": "Hide settings",
|
|
871
|
-
"settings.expand": "Show settings"
|
|
839
|
+
"source.clearTrashConfirm": "Delete permanently"
|
|
840
|
+
};
|
|
841
|
+
//#endregion
|
|
842
|
+
//#region src/client/locales.ts
|
|
843
|
+
/**
|
|
844
|
+
* locales 按视图拆分后的聚合入口:各域字典在 ./locales/<domain>.ts,
|
|
845
|
+
* 这里合并为面板消费的 zh/en。老 `from './locales.ts'` 写法保持可用,
|
|
846
|
+
* HubKey 的完备性仍由 `en: Record<HubKey, string>` 在类型层兜底
|
|
847
|
+
* (缺键即编译报错)。
|
|
848
|
+
*/
|
|
849
|
+
const zh = {
|
|
850
|
+
...zhCommon,
|
|
851
|
+
...zhSkills,
|
|
852
|
+
...zhMarket,
|
|
853
|
+
...zhSources,
|
|
854
|
+
...zhDetail,
|
|
855
|
+
...zhSettings
|
|
856
|
+
};
|
|
857
|
+
const en = {
|
|
858
|
+
...enCommon,
|
|
859
|
+
...enSkills,
|
|
860
|
+
...enMarket,
|
|
861
|
+
...enSources,
|
|
862
|
+
...enDetail,
|
|
863
|
+
...enSettings
|
|
872
864
|
};
|
|
873
865
|
/** Interpolate a {name} template with the given values. */
|
|
874
866
|
function t(dict, key, values) {
|
|
@@ -1013,8 +1005,15 @@ window.__ModuleLoader__.load({
|
|
|
1013
1005
|
* based, family precedent) plus a small error-message extractor.
|
|
1014
1006
|
*/
|
|
1015
1007
|
/** Active dictionary, picked by the document language at call time. */
|
|
1008
|
+
let cachedDictionaryLang = "";
|
|
1009
|
+
let cachedDictionary = null;
|
|
1016
1010
|
function dictionary() {
|
|
1017
|
-
|
|
1011
|
+
const key = (typeof document !== "undefined" ? document.documentElement.lang : "zh").toLowerCase().startsWith("en") ? "en" : "zh";
|
|
1012
|
+
if (cachedDictionary === null || cachedDictionaryLang !== key) {
|
|
1013
|
+
cachedDictionaryLang = key;
|
|
1014
|
+
cachedDictionary = key === "en" ? { ...en } : { ...zh };
|
|
1015
|
+
}
|
|
1016
|
+
return cachedDictionary;
|
|
1018
1017
|
}
|
|
1019
1018
|
/** Translate a key with optional {name} template params (current language). */
|
|
1020
1019
|
function tt(key, values) {
|
|
@@ -1574,6 +1573,51 @@ window.__ModuleLoader__.load({
|
|
|
1574
1573
|
]
|
|
1575
1574
|
});
|
|
1576
1575
|
}
|
|
1576
|
+
function SecretField(props) {
|
|
1577
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1578
|
+
className: settings_card_module_css_default.field,
|
|
1579
|
+
children: [
|
|
1580
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1581
|
+
className: settings_card_module_css_default.head,
|
|
1582
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
1583
|
+
className: settings_card_module_css_default.label,
|
|
1584
|
+
htmlFor: props.id,
|
|
1585
|
+
children: props.label
|
|
1586
|
+
}), props.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1587
|
+
className: settings_card_module_css_default.badges,
|
|
1588
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1589
|
+
className: settings_card_module_css_default.badge,
|
|
1590
|
+
children: props.overriddenLabel
|
|
1591
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1592
|
+
type: "button",
|
|
1593
|
+
className: settings_card_module_css_default.reset,
|
|
1594
|
+
disabled: props.disabled,
|
|
1595
|
+
onClick: props.onReset,
|
|
1596
|
+
children: props.resetLabel
|
|
1597
|
+
})]
|
|
1598
|
+
}) : null]
|
|
1599
|
+
}),
|
|
1600
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1601
|
+
id: props.id,
|
|
1602
|
+
type: "password",
|
|
1603
|
+
autoComplete: "off",
|
|
1604
|
+
spellCheck: false,
|
|
1605
|
+
className: settings_card_module_css_default.input,
|
|
1606
|
+
value: props.text,
|
|
1607
|
+
disabled: props.disabled,
|
|
1608
|
+
placeholder: props.placeholder,
|
|
1609
|
+
"aria-invalid": props.invalid === true,
|
|
1610
|
+
onChange: (event) => {
|
|
1611
|
+
props.onEdit(event.target.value);
|
|
1612
|
+
}
|
|
1613
|
+
}),
|
|
1614
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1615
|
+
className: settings_card_module_css_default.hint,
|
|
1616
|
+
children: props.hint
|
|
1617
|
+
})
|
|
1618
|
+
]
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1577
1621
|
function NumberField(props) {
|
|
1578
1622
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1579
1623
|
className: settings_card_module_css_default.field,
|
|
@@ -2361,6 +2405,26 @@ window.__ModuleLoader__.load({
|
|
|
2361
2405
|
};
|
|
2362
2406
|
}
|
|
2363
2407
|
/**
|
|
2408
|
+
* A secret field (API token): the stored value is never echoed back into the
|
|
2409
|
+
* draft (format always yields ''), so typing is always a fresh set and Reset
|
|
2410
|
+
* always unsets. An empty draft stages nothing.
|
|
2411
|
+
*/
|
|
2412
|
+
function secretField(field) {
|
|
2413
|
+
return {
|
|
2414
|
+
field,
|
|
2415
|
+
format: () => "",
|
|
2416
|
+
parse: (text) => {
|
|
2417
|
+
const trimmed = text.trim();
|
|
2418
|
+
if (trimmed === "") return void 0;
|
|
2419
|
+
if (!/^[A-Za-z0-9_-]{8,255}$/.test(trimmed)) return void 0;
|
|
2420
|
+
return {
|
|
2421
|
+
kind: "set",
|
|
2422
|
+
value: trimmed
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
/**
|
|
2364
2428
|
* A numeric field with range clamping. Integer by default: a fractional draft
|
|
2365
2429
|
* is truncated so "5.5 分钟" cannot sneak past an integer-only schema.
|
|
2366
2430
|
*/
|
|
@@ -2574,7 +2638,8 @@ window.__ModuleLoader__.load({
|
|
|
2574
2638
|
numberField("statsScanMinutes", {
|
|
2575
2639
|
min: 1,
|
|
2576
2640
|
max: 1440
|
|
2577
|
-
})
|
|
2641
|
+
}),
|
|
2642
|
+
secretField("githubToken")
|
|
2578
2643
|
]);
|
|
2579
2644
|
this.store = this.form.bind(() => this.projection());
|
|
2580
2645
|
}
|
|
@@ -2589,7 +2654,8 @@ window.__ModuleLoader__.load({
|
|
|
2589
2654
|
showUseTime: this.form.field("showUseTime"),
|
|
2590
2655
|
showGroupSummary: this.form.field("showGroupSummary"),
|
|
2591
2656
|
statsWindowDays: this.form.field("statsWindowDays"),
|
|
2592
|
-
statsScanMinutes: this.form.field("statsScanMinutes")
|
|
2657
|
+
statsScanMinutes: this.form.field("statsScanMinutes"),
|
|
2658
|
+
githubToken: this.form.field("githubToken")
|
|
2593
2659
|
};
|
|
2594
2660
|
}
|
|
2595
2661
|
/**
|
|
@@ -2757,6 +2823,22 @@ window.__ModuleLoader__.load({
|
|
|
2757
2823
|
onReset: () => {
|
|
2758
2824
|
props.resetField("statsScanMinutes");
|
|
2759
2825
|
}
|
|
2826
|
+
}),
|
|
2827
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SecretField, {
|
|
2828
|
+
id: "skill-hub-github-token",
|
|
2829
|
+
label: t("settings.githubToken"),
|
|
2830
|
+
hint: t("settings.githubTokenHint"),
|
|
2831
|
+
placeholder: t("settings.githubTokenPlaceholder"),
|
|
2832
|
+
...fieldProps,
|
|
2833
|
+
text: state.githubToken.text,
|
|
2834
|
+
overridden: state.githubToken.overridden,
|
|
2835
|
+
invalid: state.githubToken.invalid,
|
|
2836
|
+
onEdit: (text) => {
|
|
2837
|
+
props.edit("githubToken", text);
|
|
2838
|
+
},
|
|
2839
|
+
onReset: () => {
|
|
2840
|
+
props.resetField("githubToken");
|
|
2841
|
+
}
|
|
2760
2842
|
})
|
|
2761
2843
|
]
|
|
2762
2844
|
});
|
|
@@ -4383,13 +4465,14 @@ window.__ModuleLoader__.load({
|
|
|
4383
4465
|
});
|
|
4384
4466
|
}
|
|
4385
4467
|
//#endregion
|
|
4386
|
-
//#region src/client/panel/
|
|
4387
|
-
/**
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4468
|
+
//#region src/client/panel/market-format.ts
|
|
4469
|
+
/** Market tab number formatting (stars/downloads/bytes/speed). Pure, no deps. */
|
|
4470
|
+
/** Compact count for stars/downloads (1234 → 1.2k). */
|
|
4471
|
+
function formatCount(n) {
|
|
4472
|
+
if (n < 1e3) return String(n);
|
|
4473
|
+
if (n < 1e6) return (n / 1e3).toFixed(1).replace(/\.0$/, "") + "k";
|
|
4474
|
+
return (n / 1e6).toFixed(1).replace(/\.0$/, "") + "M";
|
|
4475
|
+
}
|
|
4393
4476
|
/** Compact byte size for repo preview rows. */
|
|
4394
4477
|
function formatBytes(bytes) {
|
|
4395
4478
|
if (bytes < 1024) return bytes + " B";
|
|
@@ -4402,152 +4485,580 @@ window.__ModuleLoader__.load({
|
|
|
4402
4485
|
if (bps < 1024 * 1024) return (bps / 1024).toFixed(1) + " KB/s";
|
|
4403
4486
|
return (bps / 1024 / 1024).toFixed(1) + " MB/s";
|
|
4404
4487
|
}
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
const
|
|
4410
|
-
const
|
|
4411
|
-
const
|
|
4412
|
-
const
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
}
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4488
|
+
//#endregion
|
|
4489
|
+
//#region src/client/panel/MarketSourceRow.tsx
|
|
4490
|
+
function MarketSourceRow(props) {
|
|
4491
|
+
const { hub, record } = props;
|
|
4492
|
+
const { marketCheck, sourceCheck, sourcesState, scanningRepo, syncingMarket, checkingSource, tagBusy, checkSources, checkMarket, syncMarketSource, scanRepo, removeMarketSource } = hub;
|
|
4493
|
+
const releaseCheck = marketCheck[record.repo];
|
|
4494
|
+
const skillCheck = sourceCheck[record.repo];
|
|
4495
|
+
const installedCount = sourcesState?.sources.find((source) => source.repo === record.repo)?.skills.length ?? 0;
|
|
4496
|
+
const scanning = scanningRepo === record.repo;
|
|
4497
|
+
const syncing = syncingMarket === record.repo;
|
|
4498
|
+
const checking = checkingSource === record.repo;
|
|
4499
|
+
const hasSkillUpdate = skillCheck?.changed === true && skillCheck.updated.length > 0;
|
|
4500
|
+
const hasReleaseUpdate = releaseCheck?.updateAvailable === true;
|
|
4501
|
+
const hasUpdate = hasSkillUpdate || hasReleaseUpdate;
|
|
4502
|
+
const isChecked = releaseCheck !== void 0 || skillCheck !== void 0;
|
|
4503
|
+
let updateLabel;
|
|
4504
|
+
let updateDisabled = false;
|
|
4505
|
+
let updateDanger = false;
|
|
4506
|
+
let updateAction = () => {};
|
|
4507
|
+
if (syncing) {
|
|
4508
|
+
updateLabel = tt("market.syncing");
|
|
4509
|
+
updateDisabled = true;
|
|
4510
|
+
} else if (checking) {
|
|
4511
|
+
updateLabel = tt("market.checking");
|
|
4512
|
+
updateDisabled = true;
|
|
4513
|
+
} else if (!isChecked) {
|
|
4514
|
+
updateLabel = tt("market.checkUpdate");
|
|
4515
|
+
updateAction = () => {
|
|
4516
|
+
checkSources(record.repo);
|
|
4517
|
+
checkMarket();
|
|
4518
|
+
};
|
|
4519
|
+
} else if (hasUpdate) {
|
|
4520
|
+
const count = hasSkillUpdate ? skillCheck.updated.length : 1;
|
|
4521
|
+
updateLabel = count > 1 || hasSkillUpdate ? tt("market.updateCount", { count }) : tt("market.update");
|
|
4522
|
+
updateDanger = true;
|
|
4523
|
+
updateAction = () => {
|
|
4524
|
+
syncMarketSource(record.repo);
|
|
4525
|
+
};
|
|
4526
|
+
} else {
|
|
4527
|
+
updateLabel = tt("market.upToDate");
|
|
4528
|
+
updateDisabled = true;
|
|
4529
|
+
}
|
|
4530
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4531
|
+
className: panel_module_css_default.row + " " + panel_module_css_default.rowStatic,
|
|
4532
|
+
children: [
|
|
4533
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4534
|
+
className: panel_module_css_default.rowMain,
|
|
4535
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4536
|
+
className: panel_module_css_default.rowName,
|
|
4537
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
4538
|
+
className: panel_module_css_default.sourceLink,
|
|
4539
|
+
href: "https://github.com/" + record.repo,
|
|
4540
|
+
target: "_blank",
|
|
4541
|
+
rel: "noreferrer",
|
|
4542
|
+
children: record.repo
|
|
4543
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4544
|
+
type: "button",
|
|
4545
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeSource,
|
|
4546
|
+
style: {
|
|
4547
|
+
cursor: "pointer",
|
|
4548
|
+
fontFamily: "inherit"
|
|
4549
|
+
},
|
|
4550
|
+
title: tt("market.versionHint"),
|
|
4551
|
+
onClick: () => {
|
|
4552
|
+
hub.openVersionDialog(record.repo);
|
|
4553
|
+
},
|
|
4554
|
+
children: record.ref ?? tt("market.unpinned")
|
|
4555
|
+
})]
|
|
4556
|
+
}), (() => {
|
|
4557
|
+
const stats = hub.marketStats[record.repo];
|
|
4558
|
+
const hasDeleted = skillCheck !== void 0 && skillCheck.deleted.length > 0;
|
|
4559
|
+
if (stats === void 0 && installedCount === 0 && !hasSkillUpdate && !hasDeleted && !hasReleaseUpdate) return null;
|
|
4560
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4561
|
+
className: panel_module_css_default.hintLine,
|
|
4562
|
+
style: {
|
|
4563
|
+
display: "flex",
|
|
4564
|
+
gap: 4,
|
|
4565
|
+
flexWrap: "wrap",
|
|
4566
|
+
marginTop: 3
|
|
4567
|
+
},
|
|
4466
4568
|
children: [
|
|
4467
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
4468
|
-
className: panel_module_css_default.sourceLink,
|
|
4469
|
-
href: "https://github.com/" + record.repo,
|
|
4470
|
-
target: "_blank",
|
|
4471
|
-
rel: "noreferrer",
|
|
4472
|
-
children: record.repo
|
|
4473
|
-
}),
|
|
4474
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4475
|
-
type: "button",
|
|
4476
|
-
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeSource,
|
|
4477
|
-
style: {
|
|
4478
|
-
cursor: "pointer",
|
|
4479
|
-
fontFamily: "inherit"
|
|
4480
|
-
},
|
|
4481
|
-
title: tt("market.versionHint"),
|
|
4482
|
-
onClick: () => {
|
|
4483
|
-
hub.openVersionDialog(record.repo);
|
|
4484
|
-
},
|
|
4485
|
-
children: record.ref ?? tt("market.unpinned")
|
|
4486
|
-
}),
|
|
4487
|
-
installedCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4488
|
-
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeCount,
|
|
4489
|
-
children: tt("market.installed", { count: installedCount })
|
|
4490
|
-
}) : null,
|
|
4491
4569
|
hasSkillUpdate ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4492
4570
|
className: panel_module_css_default.badge + " " + panel_module_css_default.statusUpdated,
|
|
4493
4571
|
children: tt("market.updatable", { count: skillCheck.updated.length })
|
|
4494
4572
|
}) : null,
|
|
4495
|
-
|
|
4573
|
+
hasDeleted ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4496
4574
|
className: panel_module_css_default.badge + " " + panel_module_css_default.statusError,
|
|
4497
4575
|
children: tt("market.deletedUpstream", { count: skillCheck.deleted.length })
|
|
4498
4576
|
}) : null,
|
|
4499
4577
|
hasReleaseUpdate ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4500
4578
|
className: panel_module_css_default.badge + " " + panel_module_css_default.statusUpdated,
|
|
4501
4579
|
children: releaseCheck.latestTag !== void 0 ? tt("market.newRelease", { version: releaseCheck.latestTag }) : tt("market.updated")
|
|
4580
|
+
}) : null,
|
|
4581
|
+
installedCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4582
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeCount,
|
|
4583
|
+
children: tt("market.installed", { count: installedCount })
|
|
4584
|
+
}) : null,
|
|
4585
|
+
stats !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4586
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeCount,
|
|
4587
|
+
title: tt("market.starsHint", { count: stats.stars }),
|
|
4588
|
+
children: ["★ ", formatCount(stats.stars)]
|
|
4589
|
+
}) : null,
|
|
4590
|
+
stats !== void 0 && stats.downloads > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4591
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeCount,
|
|
4592
|
+
title: tt("market.downloadsHint", { count: stats.downloads }),
|
|
4593
|
+
children: ["⭳ ", formatCount(stats.downloads)]
|
|
4502
4594
|
}) : null
|
|
4503
4595
|
]
|
|
4504
|
-
})
|
|
4505
|
-
})
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
};
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4596
|
+
});
|
|
4597
|
+
})()]
|
|
4598
|
+
}),
|
|
4599
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4600
|
+
type: "button",
|
|
4601
|
+
className: panel_module_css_default.button + " " + panel_module_css_default.primary,
|
|
4602
|
+
style: {
|
|
4603
|
+
padding: "4px 10px",
|
|
4604
|
+
fontSize: 12
|
|
4605
|
+
},
|
|
4606
|
+
disabled: scanning,
|
|
4607
|
+
onClick: () => {
|
|
4608
|
+
scanRepo(record.repo);
|
|
4609
|
+
},
|
|
4610
|
+
children: scanning ? tt("market.scanning") : tt("market.scan")
|
|
4611
|
+
}),
|
|
4612
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4613
|
+
type: "button",
|
|
4614
|
+
className: panel_module_css_default.opBtn + (updateDanger ? " " + panel_module_css_default.opDanger : ""),
|
|
4615
|
+
disabled: updateDisabled,
|
|
4616
|
+
title: hasUpdate ? "将同步到上游最新版并提示可更新的本地技能" : void 0,
|
|
4617
|
+
onClick: updateAction,
|
|
4618
|
+
children: updateLabel
|
|
4619
|
+
}),
|
|
4620
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4621
|
+
type: "button",
|
|
4622
|
+
className: panel_module_css_default.opBtn,
|
|
4623
|
+
disabled: tagBusy,
|
|
4624
|
+
title: tt("market.removeHint"),
|
|
4625
|
+
"aria-label": tt("market.deleteSource"),
|
|
4626
|
+
onClick: () => {
|
|
4627
|
+
removeMarketSource(record.repo);
|
|
4628
|
+
},
|
|
4629
|
+
style: {
|
|
4630
|
+
padding: "4px 8px",
|
|
4631
|
+
minWidth: 28
|
|
4632
|
+
},
|
|
4633
|
+
children: "×"
|
|
4634
|
+
})
|
|
4635
|
+
]
|
|
4636
|
+
});
|
|
4637
|
+
}
|
|
4638
|
+
//#endregion
|
|
4639
|
+
//#region src/client/panel/RepoScanCard.tsx
|
|
4640
|
+
/**
|
|
4641
|
+
* RepoScanCard — 扫描结果归属卡片:扫描中/错误/就绪(搜索+过滤+分页+
|
|
4642
|
+
* 勾选导入+进度+结果)都包在这里。搜索/过滤/分页是卡片级本地状态,
|
|
4643
|
+
* 随卡片挂载而生、关闭而灭。从 MarketView.tsx 原样搬出,渲染逻辑不变。
|
|
4644
|
+
*/
|
|
4645
|
+
function RepoScanCard(props) {
|
|
4646
|
+
const { hub } = props;
|
|
4647
|
+
const { repoDiscoverState, scanningRepo, repoResult, repoSelected, setRepoSelected, repoImporting, toggleRepoSelected, importRepo, cancelImport } = hub;
|
|
4648
|
+
const [repoSearch, setRepoSearch] = (0, react.useState)("");
|
|
4649
|
+
const [repoFilter, setRepoFilter] = (0, react.useState)("all");
|
|
4650
|
+
const [visibleCount, setVisibleCount] = (0, react.useState)(50);
|
|
4651
|
+
(0, react.useEffect)(() => {
|
|
4652
|
+
setVisibleCount(50);
|
|
4653
|
+
}, [
|
|
4654
|
+
repoDiscoverState,
|
|
4655
|
+
repoSearch,
|
|
4656
|
+
repoFilter
|
|
4657
|
+
]);
|
|
4658
|
+
if (repoDiscoverState.status === "idle" && repoResult === null) return null;
|
|
4659
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4660
|
+
className: panel_module_css_default.section,
|
|
4661
|
+
style: {
|
|
4662
|
+
borderLeft: "3px solid var(--hub-model, #2f81f7)",
|
|
4663
|
+
background: "rgba(47,129,247,0.04)"
|
|
4664
|
+
},
|
|
4665
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4666
|
+
className: panel_module_css_default.sectionTitle + " " + panel_module_css_default.sectionHeadRow,
|
|
4667
|
+
style: { margin: "10px 14px 6px" },
|
|
4668
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4669
|
+
className: panel_module_css_default.sectionTitleFill,
|
|
4670
|
+
style: {
|
|
4671
|
+
display: "flex",
|
|
4672
|
+
alignItems: "center",
|
|
4673
|
+
gap: 6
|
|
4674
|
+
},
|
|
4675
|
+
children: ["扫描结果", /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4676
|
+
style: {
|
|
4677
|
+
fontWeight: 400,
|
|
4678
|
+
opacity: .75
|
|
4679
|
+
},
|
|
4680
|
+
children: [
|
|
4681
|
+
"— ",
|
|
4682
|
+
repoDiscoverState.status === "ready" ? repoDiscoverState.data.repo : repoDiscoverState.status === "error" ? scanningRepo ?? "" : scanningRepo ?? "",
|
|
4683
|
+
repoDiscoverState.status === "ready" && repoDiscoverState.data.ref !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4684
|
+
style: {
|
|
4685
|
+
opacity: .5,
|
|
4686
|
+
marginLeft: 6
|
|
4687
|
+
},
|
|
4688
|
+
children: ["ref ", repoDiscoverState.data.ref]
|
|
4689
|
+
}) : null
|
|
4690
|
+
]
|
|
4691
|
+
})]
|
|
4692
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4693
|
+
type: "button",
|
|
4694
|
+
className: panel_module_css_default.opBtn,
|
|
4695
|
+
onClick: () => {
|
|
4696
|
+
hub.clearScan();
|
|
4697
|
+
},
|
|
4698
|
+
children: "关闭"
|
|
4699
|
+
})]
|
|
4700
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4701
|
+
style: { padding: "0 12px 12px" },
|
|
4702
|
+
children: [
|
|
4703
|
+
repoDiscoverState.status === "scanning" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4704
|
+
className: panel_module_css_default.empty,
|
|
4705
|
+
style: { padding: "12px 0" },
|
|
4706
|
+
children: tt("market.scanning")
|
|
4707
|
+
}) : null,
|
|
4708
|
+
repoDiscoverState.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4709
|
+
className: panel_module_css_default.errorBanner,
|
|
4710
|
+
children: repoDiscoverState.message
|
|
4711
|
+
}) : null,
|
|
4712
|
+
repoDiscoverState.status === "ready" ? (() => {
|
|
4713
|
+
const entries = repoDiscoverState.data.entries;
|
|
4714
|
+
const installedNames = /* @__PURE__ */ new Set([
|
|
4715
|
+
...(hub.catalog?.skills ?? []).map((s) => s.name),
|
|
4716
|
+
...(hub.catalog?.disabled ?? []).map((d) => d.name),
|
|
4717
|
+
...(repoResult?.imported ?? []).map((r) => r.name),
|
|
4718
|
+
...(repoResult?.skipped ?? []).map((r) => r.name)
|
|
4719
|
+
]);
|
|
4720
|
+
const isExisting = (entry) => entry.existing || installedNames.has(entry.name);
|
|
4721
|
+
const selected = entries.filter((entry) => repoSelected.has(entry.path) && !isExisting(entry));
|
|
4722
|
+
const selectedBytes = selected.reduce((s, e) => s + e.totalBytes, 0);
|
|
4723
|
+
const filtered = entries.filter((entry) => {
|
|
4724
|
+
if (repoFilter !== "all" && entry.root !== repoFilter) return false;
|
|
4725
|
+
if (repoSearch.trim() !== "" && !entry.name.toLowerCase().includes(repoSearch.trim().toLowerCase())) return false;
|
|
4726
|
+
return true;
|
|
4727
|
+
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
4728
|
+
const paged = filtered.slice(0, visibleCount);
|
|
4729
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
4730
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
4731
|
+
className: panel_module_css_default.hintLine + " " + panel_module_css_default.hintInline,
|
|
4732
|
+
children: [
|
|
4733
|
+
tt("repo.ready", { count: entries.length }),
|
|
4734
|
+
" · ",
|
|
4735
|
+
repoDiscoverState.data.ref !== null ? `ref ${repoDiscoverState.data.ref}` : ""
|
|
4736
|
+
]
|
|
4737
|
+
}),
|
|
4738
|
+
repoDiscoverState.data.truncated === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4739
|
+
className: panel_module_css_default.errorBanner,
|
|
4740
|
+
style: {
|
|
4741
|
+
background: "rgba(210,153,34,0.08)",
|
|
4742
|
+
color: "#d29922",
|
|
4743
|
+
borderLeft: "3px solid #d29922"
|
|
4744
|
+
},
|
|
4745
|
+
children: tt("repo.truncated")
|
|
4746
|
+
}) : null,
|
|
4747
|
+
entries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4748
|
+
className: panel_module_css_default.empty,
|
|
4749
|
+
children: tt("repo.empty")
|
|
4750
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
4751
|
+
(() => {
|
|
4752
|
+
const order = /* @__PURE__ */ new Map([["skills", 0], ["design-templates", 1]]);
|
|
4753
|
+
const roots = [...new Set(entries.map((e) => e.root))].sort((a, b) => {
|
|
4754
|
+
const ao = order.get(a) ?? 99;
|
|
4755
|
+
const bo = order.get(b) ?? 99;
|
|
4756
|
+
return ao !== bo ? ao - bo : a.localeCompare(b);
|
|
4757
|
+
});
|
|
4758
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4759
|
+
for (const e of entries) counts.set(e.root, (counts.get(e.root) ?? 0) + 1);
|
|
4760
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4761
|
+
style: {
|
|
4762
|
+
display: "flex",
|
|
4763
|
+
gap: 8,
|
|
4764
|
+
marginBottom: 8,
|
|
4765
|
+
flexWrap: "wrap"
|
|
4766
|
+
},
|
|
4767
|
+
children: [
|
|
4768
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4769
|
+
className: panel_module_css_default.input,
|
|
4770
|
+
style: {
|
|
4771
|
+
flex: 1,
|
|
4772
|
+
minWidth: 140
|
|
4773
|
+
},
|
|
4774
|
+
value: repoSearch,
|
|
4775
|
+
onChange: (e) => setRepoSearch(e.target.value),
|
|
4776
|
+
placeholder: "搜索技能名…"
|
|
4777
|
+
}),
|
|
4778
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4779
|
+
type: "button",
|
|
4780
|
+
className: panel_module_css_default.button + (repoFilter === "all" ? " " + panel_module_css_default.primary : ""),
|
|
4781
|
+
style: {
|
|
4782
|
+
padding: "6px 10px",
|
|
4783
|
+
fontSize: 12
|
|
4784
|
+
},
|
|
4785
|
+
onClick: () => setRepoFilter("all"),
|
|
4786
|
+
children: ["全部 ", entries.length]
|
|
4787
|
+
}),
|
|
4788
|
+
roots.map((root) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4789
|
+
type: "button",
|
|
4790
|
+
className: panel_module_css_default.button + (repoFilter === root ? " " + panel_module_css_default.primary : ""),
|
|
4791
|
+
style: {
|
|
4792
|
+
padding: "6px 10px",
|
|
4793
|
+
fontSize: 12
|
|
4794
|
+
},
|
|
4795
|
+
onClick: () => setRepoFilter(root),
|
|
4796
|
+
children: [
|
|
4797
|
+
root,
|
|
4798
|
+
" ",
|
|
4799
|
+
counts.get(root) ?? 0
|
|
4800
|
+
]
|
|
4801
|
+
}, root))
|
|
4802
|
+
]
|
|
4803
|
+
});
|
|
4804
|
+
})(),
|
|
4805
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4806
|
+
className: panel_module_css_default.hintLine,
|
|
4807
|
+
style: {
|
|
4808
|
+
display: "flex",
|
|
4809
|
+
justifyContent: "space-between",
|
|
4810
|
+
flexWrap: "wrap",
|
|
4811
|
+
gap: 8
|
|
4812
|
+
},
|
|
4813
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
4814
|
+
"已选 ",
|
|
4815
|
+
selected.length,
|
|
4816
|
+
"/",
|
|
4817
|
+
entries.length,
|
|
4818
|
+
" · ",
|
|
4819
|
+
formatBytes(selectedBytes),
|
|
4820
|
+
" · 显示 ",
|
|
4821
|
+
paged.length,
|
|
4822
|
+
"/",
|
|
4823
|
+
filtered.length,
|
|
4824
|
+
"(过滤后)"
|
|
4825
|
+
] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4826
|
+
style: {
|
|
4827
|
+
display: "flex",
|
|
4828
|
+
gap: 6
|
|
4829
|
+
},
|
|
4830
|
+
children: [
|
|
4831
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4832
|
+
type: "button",
|
|
4833
|
+
className: panel_module_css_default.button,
|
|
4834
|
+
style: {
|
|
4835
|
+
padding: "4px 8px",
|
|
4836
|
+
fontSize: 11
|
|
4837
|
+
},
|
|
4838
|
+
onClick: () => {
|
|
4839
|
+
setRepoSelected(new Set(filtered.filter((e) => !isExisting(e)).slice(0, visibleCount).map((e) => e.path)));
|
|
4840
|
+
},
|
|
4841
|
+
children: "全选当前显示"
|
|
4842
|
+
}),
|
|
4843
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4844
|
+
type: "button",
|
|
4845
|
+
className: panel_module_css_default.button,
|
|
4846
|
+
style: {
|
|
4847
|
+
padding: "4px 8px",
|
|
4848
|
+
fontSize: 11
|
|
4849
|
+
},
|
|
4850
|
+
onClick: () => {
|
|
4851
|
+
setRepoSelected(new Set(entries.filter((e) => !isExisting(e)).map((e) => e.path)));
|
|
4852
|
+
},
|
|
4853
|
+
children: ["全选全部 ", entries.filter((e) => !isExisting(e)).length]
|
|
4854
|
+
}),
|
|
4855
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4856
|
+
type: "button",
|
|
4857
|
+
className: panel_module_css_default.button,
|
|
4858
|
+
style: {
|
|
4859
|
+
padding: "4px 8px",
|
|
4860
|
+
fontSize: 11
|
|
4861
|
+
},
|
|
4862
|
+
onClick: () => {
|
|
4863
|
+
setRepoSelected(/* @__PURE__ */ new Set());
|
|
4864
|
+
},
|
|
4865
|
+
children: tt("repo.clearAll")
|
|
4866
|
+
})
|
|
4867
|
+
]
|
|
4868
|
+
})]
|
|
4869
|
+
}),
|
|
4870
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4871
|
+
className: panel_module_css_default.scanList,
|
|
4872
|
+
children: paged.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4873
|
+
className: panel_module_css_default.empty,
|
|
4874
|
+
style: { padding: 20 },
|
|
4875
|
+
children: "无匹配技能"
|
|
4876
|
+
}) : paged.map((entry) => {
|
|
4877
|
+
const existing = isExisting(entry);
|
|
4878
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
4879
|
+
className: panel_module_css_default.row + (existing ? " " + panel_module_css_default.rowMuted : ""),
|
|
4880
|
+
children: [
|
|
4881
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4882
|
+
type: "checkbox",
|
|
4883
|
+
checked: repoSelected.has(entry.path),
|
|
4884
|
+
disabled: existing,
|
|
4885
|
+
onChange: (event) => {
|
|
4886
|
+
toggleRepoSelected(entry.path, event.target.checked);
|
|
4887
|
+
}
|
|
4888
|
+
}),
|
|
4889
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4890
|
+
className: panel_module_css_default.rowMain,
|
|
4891
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4892
|
+
className: panel_module_css_default.rowName,
|
|
4893
|
+
children: entry.name
|
|
4894
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4895
|
+
className: panel_module_css_default.rowDesc,
|
|
4896
|
+
children: [
|
|
4897
|
+
entry.dir,
|
|
4898
|
+
" · ",
|
|
4899
|
+
tt("repo.files", {
|
|
4900
|
+
count: entry.fileCount,
|
|
4901
|
+
size: formatBytes(entry.totalBytes)
|
|
4902
|
+
})
|
|
4903
|
+
]
|
|
4904
|
+
})]
|
|
4905
|
+
}),
|
|
4906
|
+
existing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4907
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeReadonly,
|
|
4908
|
+
children: tt("repo.existing")
|
|
4909
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4910
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeSource,
|
|
4911
|
+
children: entry.origin
|
|
4912
|
+
})
|
|
4913
|
+
]
|
|
4914
|
+
}, entry.path);
|
|
4915
|
+
})
|
|
4916
|
+
}),
|
|
4917
|
+
visibleCount < filtered.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4918
|
+
style: {
|
|
4919
|
+
textAlign: "center",
|
|
4920
|
+
marginTop: 8
|
|
4921
|
+
},
|
|
4922
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4923
|
+
type: "button",
|
|
4924
|
+
className: panel_module_css_default.button,
|
|
4925
|
+
onClick: () => setVisibleCount((n) => n + 50),
|
|
4926
|
+
children: [
|
|
4927
|
+
"加载更多 50 (剩余 ",
|
|
4928
|
+
filtered.length - visibleCount,
|
|
4929
|
+
")"
|
|
4930
|
+
]
|
|
4931
|
+
})
|
|
4932
|
+
}) : null,
|
|
4933
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4934
|
+
className: panel_module_css_default.buttons + " " + panel_module_css_default.actionsTop,
|
|
4935
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4936
|
+
type: "button",
|
|
4937
|
+
className: panel_module_css_default.button + " " + panel_module_css_default.primary,
|
|
4938
|
+
disabled: selected.length === 0 || repoImporting,
|
|
4939
|
+
onClick: () => {
|
|
4940
|
+
importRepo();
|
|
4941
|
+
},
|
|
4942
|
+
children: repoImporting ? tt("repo.importing") : `${tt("repo.import", { count: selected.length })} · ${formatBytes(selectedBytes)}`
|
|
4943
|
+
}), repoImporting ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4944
|
+
type: "button",
|
|
4945
|
+
className: panel_module_css_default.button,
|
|
4946
|
+
onClick: () => {
|
|
4947
|
+
cancelImport();
|
|
4948
|
+
},
|
|
4949
|
+
children: tt("repo.cancel")
|
|
4950
|
+
}) : null]
|
|
4951
|
+
}),
|
|
4952
|
+
repoResult !== null && repoResult.status === "running" ? (() => {
|
|
4953
|
+
const pct = repoResult.totalBytes > 0 ? Math.min(100, Math.round(repoResult.downloadedBytes / repoResult.totalBytes * 100)) : repoResult.total > 0 ? Math.round(repoResult.done / repoResult.total * 100) : 0;
|
|
4954
|
+
const speed = formatSpeed(repoResult.bytesPerSecond);
|
|
4955
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4956
|
+
className: panel_module_css_default.formSuccess + " " + panel_module_css_default.actionsTop,
|
|
4957
|
+
children: [
|
|
4958
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4959
|
+
style: {
|
|
4960
|
+
display: "flex",
|
|
4961
|
+
alignItems: "center",
|
|
4962
|
+
gap: 8,
|
|
4963
|
+
marginBottom: 6
|
|
4964
|
+
},
|
|
4965
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4966
|
+
className: panel_module_css_default.scanProgressTrack,
|
|
4967
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4968
|
+
className: panel_module_css_default.scanProgressFill,
|
|
4969
|
+
style: { width: `${pct}%` }
|
|
4970
|
+
})
|
|
4971
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4972
|
+
style: {
|
|
4973
|
+
fontSize: 11,
|
|
4974
|
+
opacity: .75,
|
|
4975
|
+
minWidth: 32,
|
|
4976
|
+
textAlign: "right"
|
|
4977
|
+
},
|
|
4978
|
+
children: [pct, "%"]
|
|
4979
|
+
})]
|
|
4980
|
+
}),
|
|
4981
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4982
|
+
className: panel_module_css_default.hintLine,
|
|
4983
|
+
children: [
|
|
4984
|
+
formatBytes(repoResult.downloadedBytes),
|
|
4985
|
+
" / ",
|
|
4986
|
+
formatBytes(repoResult.totalBytes),
|
|
4987
|
+
speed !== "" ? ` · ${speed}` : "",
|
|
4988
|
+
" · ",
|
|
4989
|
+
repoResult.done,
|
|
4990
|
+
"/",
|
|
4991
|
+
repoResult.total,
|
|
4992
|
+
" 个技能"
|
|
4993
|
+
]
|
|
4994
|
+
}),
|
|
4995
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4996
|
+
className: panel_module_css_default.hintLine,
|
|
4997
|
+
style: { marginTop: 4 },
|
|
4998
|
+
children: [repoResult.current !== void 0 ? tt("repo.importingCurrent", {
|
|
4999
|
+
name: repoResult.current,
|
|
5000
|
+
done: repoResult.done + 1,
|
|
5001
|
+
total: repoResult.total
|
|
5002
|
+
}) : tt("repo.importing"), repoResult.currentFile !== void 0 ? ` · ${repoResult.currentFile}` : ""]
|
|
5003
|
+
}),
|
|
5004
|
+
repoResult.failed.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5005
|
+
className: panel_module_css_default.errorBanner,
|
|
5006
|
+
style: { marginTop: 6 },
|
|
5007
|
+
children: repoResult.failed.map((f) => `${f.name}: ${f.error}`).join("; ")
|
|
5008
|
+
}) : null
|
|
5009
|
+
]
|
|
5010
|
+
});
|
|
5011
|
+
})() : null
|
|
5012
|
+
] })
|
|
5013
|
+
] });
|
|
5014
|
+
})() : null,
|
|
5015
|
+
repoResult !== null && repoResult.status !== "running" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5016
|
+
className: repoResult.status === "cancelled" ? panel_module_css_default.errorBanner + " " + panel_module_css_default.actionsTop : panel_module_css_default.formSuccess + " " + panel_module_css_default.actionsTop,
|
|
5017
|
+
children: [
|
|
5018
|
+
repoResult.status === "cancelled" ? tt("repo.cancelled", {
|
|
5019
|
+
imported: repoResult.imported.length,
|
|
5020
|
+
total: repoResult.total
|
|
5021
|
+
}) : `${tt("repo.imported", { count: repoResult.imported.length })} · ${tt("repo.skippedExisting", { count: repoResult.skipped.length })} · ${tt("repo.failed", { count: repoResult.failed.length })}`,
|
|
5022
|
+
repoResult.failed.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5023
|
+
style: { marginTop: 6 },
|
|
5024
|
+
children: repoResult.failed.map((f) => `${f.name}: ${f.error}`).join("; ")
|
|
5025
|
+
}) : null,
|
|
5026
|
+
repoResult.status === "error" && repoResult.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5027
|
+
style: { marginTop: 6 },
|
|
5028
|
+
children: repoResult.error
|
|
5029
|
+
}) : null
|
|
5030
|
+
]
|
|
5031
|
+
}) : null
|
|
5032
|
+
]
|
|
5033
|
+
})]
|
|
5034
|
+
});
|
|
5035
|
+
}
|
|
5036
|
+
//#endregion
|
|
5037
|
+
//#region src/client/panel/MarketView.tsx
|
|
5038
|
+
/**
|
|
5039
|
+
* Market tab: one unified market list — built-in catalog entries (add
|
|
5040
|
+
* button while not yet added) and the user's market sources (full state
|
|
5041
|
+
* badges + actions once added, plus custom sources), with check-all and
|
|
5042
|
+
* update-all actions and the repo scan result with the importable checklist.
|
|
5043
|
+
* 行渲染在 MarketSourceRow,扫描卡片在 RepoScanCard,数字格式在 market-format。
|
|
5044
|
+
*/
|
|
5045
|
+
function MarketView(props) {
|
|
5046
|
+
const { hub } = props;
|
|
5047
|
+
const { marketState, sourceCheck, scanningRepo, newSourceName, setNewSourceName, tagBusy, addSource, addMarketSource, checkMarket, checkSources, updateAllDialog, setUpdateAllDialog, updateAll } = hub;
|
|
5048
|
+
/** 有可更新技能的来源(全部更新按钮与确认列表共用)。 */
|
|
5049
|
+
const updatableRepos = Object.entries(sourceCheck).filter(([, check]) => check.changed && check.updated.length > 0);
|
|
5050
|
+
(0, react.useEffect)(() => {
|
|
5051
|
+
hub.loadMarketStats();
|
|
5052
|
+
}, []);
|
|
5053
|
+
const rows = [];
|
|
5054
|
+
for (const entry of MARKET_CATALOG) {
|
|
5055
|
+
const record = marketState.repos.find((item) => item.repo === entry.repo);
|
|
5056
|
+
if (record !== void 0) rows.push({
|
|
4549
5057
|
key: entry.repo,
|
|
4550
|
-
element:
|
|
5058
|
+
element: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarketSourceRow, {
|
|
5059
|
+
hub,
|
|
5060
|
+
record
|
|
5061
|
+
})
|
|
4551
5062
|
});
|
|
4552
5063
|
else {
|
|
4553
5064
|
const busy = scanningRepo !== null || tagBusy;
|
|
@@ -4587,7 +5098,10 @@ window.__ModuleLoader__.load({
|
|
|
4587
5098
|
if (MARKET_CATALOG.some((entry) => entry.repo === record.repo)) continue;
|
|
4588
5099
|
rows.push({
|
|
4589
5100
|
key: record.repo,
|
|
4590
|
-
element:
|
|
5101
|
+
element: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarketSourceRow, {
|
|
5102
|
+
hub,
|
|
5103
|
+
record
|
|
5104
|
+
})
|
|
4591
5105
|
});
|
|
4592
5106
|
}
|
|
4593
5107
|
const unaddedCatalog = MARKET_CATALOG.filter((entry) => !marketState.repos.some((record) => record.repo === entry.repo));
|
|
@@ -4662,393 +5176,18 @@ window.__ModuleLoader__.load({
|
|
|
4662
5176
|
marketState.status !== "loading" ? rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: row.element }, row.key)) : null
|
|
4663
5177
|
]
|
|
4664
5178
|
}),
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
display: "flex",
|
|
4678
|
-
alignItems: "center",
|
|
4679
|
-
gap: 6
|
|
4680
|
-
},
|
|
4681
|
-
children: ["扫描结果", /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4682
|
-
style: {
|
|
4683
|
-
fontWeight: 400,
|
|
4684
|
-
opacity: .75
|
|
4685
|
-
},
|
|
4686
|
-
children: [
|
|
4687
|
-
"— ",
|
|
4688
|
-
repoDiscoverState.status === "ready" ? repoDiscoverState.data.repo : repoDiscoverState.status === "error" ? scanningRepo ?? "" : scanningRepo ?? "",
|
|
4689
|
-
repoDiscoverState.status === "ready" && repoDiscoverState.data.ref !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4690
|
-
style: {
|
|
4691
|
-
opacity: .5,
|
|
4692
|
-
marginLeft: 6
|
|
4693
|
-
},
|
|
4694
|
-
children: ["ref ", repoDiscoverState.data.ref]
|
|
4695
|
-
}) : null
|
|
4696
|
-
]
|
|
4697
|
-
})]
|
|
4698
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4699
|
-
type: "button",
|
|
4700
|
-
className: panel_module_css_default.opBtn,
|
|
4701
|
-
onClick: () => {
|
|
4702
|
-
hub.clearScan();
|
|
4703
|
-
},
|
|
4704
|
-
children: "关闭"
|
|
4705
|
-
})]
|
|
4706
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4707
|
-
style: { padding: "0 12px 12px" },
|
|
4708
|
-
children: [
|
|
4709
|
-
repoDiscoverState.status === "scanning" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4710
|
-
className: panel_module_css_default.empty,
|
|
4711
|
-
style: { padding: "12px 0" },
|
|
4712
|
-
children: tt("market.scanning")
|
|
4713
|
-
}) : null,
|
|
4714
|
-
repoDiscoverState.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4715
|
-
className: panel_module_css_default.errorBanner,
|
|
4716
|
-
children: repoDiscoverState.message
|
|
4717
|
-
}) : null,
|
|
4718
|
-
repoDiscoverState.status === "ready" ? (() => {
|
|
4719
|
-
const entries = repoDiscoverState.data.entries;
|
|
4720
|
-
const installedNames = /* @__PURE__ */ new Set([
|
|
4721
|
-
...(hub.catalog?.skills ?? []).map((s) => s.name),
|
|
4722
|
-
...(hub.catalog?.disabled ?? []).map((d) => d.name),
|
|
4723
|
-
...(repoResult?.imported ?? []).map((r) => r.name),
|
|
4724
|
-
...(repoResult?.skipped ?? []).map((r) => r.name)
|
|
4725
|
-
]);
|
|
4726
|
-
const isExisting = (entry) => entry.existing || installedNames.has(entry.name);
|
|
4727
|
-
const selected = entries.filter((entry) => repoSelected.has(entry.path) && !isExisting(entry));
|
|
4728
|
-
const selectedBytes = selected.reduce((s, e) => s + e.totalBytes, 0);
|
|
4729
|
-
const filtered = entries.filter((entry) => {
|
|
4730
|
-
if (repoFilter !== "all" && entry.root !== repoFilter) return false;
|
|
4731
|
-
if (repoSearch.trim() !== "" && !entry.name.toLowerCase().includes(repoSearch.trim().toLowerCase())) return false;
|
|
4732
|
-
return true;
|
|
4733
|
-
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
4734
|
-
const paged = filtered.slice(0, visibleCount);
|
|
4735
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
4736
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
4737
|
-
className: panel_module_css_default.hintLine + " " + panel_module_css_default.hintInline,
|
|
4738
|
-
children: [
|
|
4739
|
-
tt("repo.ready", { count: entries.length }),
|
|
4740
|
-
" · ",
|
|
4741
|
-
repoDiscoverState.data.ref !== null ? `ref ${repoDiscoverState.data.ref}` : ""
|
|
4742
|
-
]
|
|
4743
|
-
}),
|
|
4744
|
-
repoDiscoverState.data.truncated === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4745
|
-
className: panel_module_css_default.errorBanner,
|
|
4746
|
-
style: {
|
|
4747
|
-
background: "rgba(210,153,34,0.08)",
|
|
4748
|
-
color: "#d29922",
|
|
4749
|
-
borderLeft: "3px solid #d29922"
|
|
4750
|
-
},
|
|
4751
|
-
children: tt("repo.truncated")
|
|
4752
|
-
}) : null,
|
|
4753
|
-
entries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4754
|
-
className: panel_module_css_default.empty,
|
|
4755
|
-
children: tt("repo.empty")
|
|
4756
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
4757
|
-
(() => {
|
|
4758
|
-
const order = /* @__PURE__ */ new Map([["skills", 0], ["design-templates", 1]]);
|
|
4759
|
-
const roots = [...new Set(entries.map((e) => e.root))].sort((a, b) => {
|
|
4760
|
-
const ao = order.get(a) ?? 99;
|
|
4761
|
-
const bo = order.get(b) ?? 99;
|
|
4762
|
-
return ao !== bo ? ao - bo : a.localeCompare(b);
|
|
4763
|
-
});
|
|
4764
|
-
const counts = /* @__PURE__ */ new Map();
|
|
4765
|
-
for (const e of entries) counts.set(e.root, (counts.get(e.root) ?? 0) + 1);
|
|
4766
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4767
|
-
style: {
|
|
4768
|
-
display: "flex",
|
|
4769
|
-
gap: 8,
|
|
4770
|
-
marginBottom: 8,
|
|
4771
|
-
flexWrap: "wrap"
|
|
4772
|
-
},
|
|
4773
|
-
children: [
|
|
4774
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4775
|
-
className: panel_module_css_default.input,
|
|
4776
|
-
style: {
|
|
4777
|
-
flex: 1,
|
|
4778
|
-
minWidth: 140
|
|
4779
|
-
},
|
|
4780
|
-
value: repoSearch,
|
|
4781
|
-
onChange: (e) => setRepoSearch(e.target.value),
|
|
4782
|
-
placeholder: "搜索技能名…"
|
|
4783
|
-
}),
|
|
4784
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4785
|
-
type: "button",
|
|
4786
|
-
className: panel_module_css_default.button + (repoFilter === "all" ? " " + panel_module_css_default.primary : ""),
|
|
4787
|
-
style: {
|
|
4788
|
-
padding: "6px 10px",
|
|
4789
|
-
fontSize: 12
|
|
4790
|
-
},
|
|
4791
|
-
onClick: () => setRepoFilter("all"),
|
|
4792
|
-
children: ["全部 ", entries.length]
|
|
4793
|
-
}),
|
|
4794
|
-
roots.map((root) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4795
|
-
type: "button",
|
|
4796
|
-
className: panel_module_css_default.button + (repoFilter === root ? " " + panel_module_css_default.primary : ""),
|
|
4797
|
-
style: {
|
|
4798
|
-
padding: "6px 10px",
|
|
4799
|
-
fontSize: 12
|
|
4800
|
-
},
|
|
4801
|
-
onClick: () => setRepoFilter(root),
|
|
4802
|
-
children: [
|
|
4803
|
-
root,
|
|
4804
|
-
" ",
|
|
4805
|
-
counts.get(root) ?? 0
|
|
4806
|
-
]
|
|
4807
|
-
}, root))
|
|
4808
|
-
]
|
|
4809
|
-
});
|
|
4810
|
-
})(),
|
|
4811
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4812
|
-
className: panel_module_css_default.hintLine,
|
|
4813
|
-
style: {
|
|
4814
|
-
display: "flex",
|
|
4815
|
-
justifyContent: "space-between",
|
|
4816
|
-
flexWrap: "wrap",
|
|
4817
|
-
gap: 8
|
|
4818
|
-
},
|
|
4819
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
4820
|
-
"已选 ",
|
|
4821
|
-
selected.length,
|
|
4822
|
-
"/",
|
|
4823
|
-
entries.length,
|
|
4824
|
-
" · ",
|
|
4825
|
-
formatBytes(selectedBytes),
|
|
4826
|
-
" · 显示 ",
|
|
4827
|
-
paged.length,
|
|
4828
|
-
"/",
|
|
4829
|
-
filtered.length,
|
|
4830
|
-
"(过滤后)"
|
|
4831
|
-
] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4832
|
-
style: {
|
|
4833
|
-
display: "flex",
|
|
4834
|
-
gap: 6
|
|
4835
|
-
},
|
|
4836
|
-
children: [
|
|
4837
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4838
|
-
type: "button",
|
|
4839
|
-
className: panel_module_css_default.button,
|
|
4840
|
-
style: {
|
|
4841
|
-
padding: "4px 8px",
|
|
4842
|
-
fontSize: 11
|
|
4843
|
-
},
|
|
4844
|
-
onClick: () => {
|
|
4845
|
-
setRepoSelected(new Set(filtered.filter((e) => !isExisting(e)).slice(0, visibleCount).map((e) => e.path)));
|
|
4846
|
-
},
|
|
4847
|
-
children: "全选当前显示"
|
|
4848
|
-
}),
|
|
4849
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4850
|
-
type: "button",
|
|
4851
|
-
className: panel_module_css_default.button,
|
|
4852
|
-
style: {
|
|
4853
|
-
padding: "4px 8px",
|
|
4854
|
-
fontSize: 11
|
|
4855
|
-
},
|
|
4856
|
-
onClick: () => {
|
|
4857
|
-
setRepoSelected(new Set(entries.filter((e) => !isExisting(e)).map((e) => e.path)));
|
|
4858
|
-
},
|
|
4859
|
-
children: ["全选全部 ", entries.filter((e) => !isExisting(e)).length]
|
|
4860
|
-
}),
|
|
4861
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4862
|
-
type: "button",
|
|
4863
|
-
className: panel_module_css_default.button,
|
|
4864
|
-
style: {
|
|
4865
|
-
padding: "4px 8px",
|
|
4866
|
-
fontSize: 11
|
|
4867
|
-
},
|
|
4868
|
-
onClick: () => {
|
|
4869
|
-
setRepoSelected(/* @__PURE__ */ new Set());
|
|
4870
|
-
},
|
|
4871
|
-
children: tt("repo.clearAll")
|
|
4872
|
-
})
|
|
4873
|
-
]
|
|
4874
|
-
})]
|
|
4875
|
-
}),
|
|
4876
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4877
|
-
className: panel_module_css_default.scanList,
|
|
4878
|
-
children: paged.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4879
|
-
className: panel_module_css_default.empty,
|
|
4880
|
-
style: { padding: 20 },
|
|
4881
|
-
children: "无匹配技能"
|
|
4882
|
-
}) : paged.map((entry) => {
|
|
4883
|
-
const existing = isExisting(entry);
|
|
4884
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
4885
|
-
className: panel_module_css_default.row + (existing ? " " + panel_module_css_default.rowMuted : ""),
|
|
4886
|
-
children: [
|
|
4887
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4888
|
-
type: "checkbox",
|
|
4889
|
-
checked: repoSelected.has(entry.path),
|
|
4890
|
-
disabled: existing,
|
|
4891
|
-
onChange: (event) => {
|
|
4892
|
-
toggleRepoSelected(entry.path, event.target.checked);
|
|
4893
|
-
}
|
|
4894
|
-
}),
|
|
4895
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4896
|
-
className: panel_module_css_default.rowMain,
|
|
4897
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4898
|
-
className: panel_module_css_default.rowName,
|
|
4899
|
-
children: entry.name
|
|
4900
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4901
|
-
className: panel_module_css_default.rowDesc,
|
|
4902
|
-
children: [
|
|
4903
|
-
entry.dir,
|
|
4904
|
-
" · ",
|
|
4905
|
-
tt("repo.files", {
|
|
4906
|
-
count: entry.fileCount,
|
|
4907
|
-
size: formatBytes(entry.totalBytes)
|
|
4908
|
-
})
|
|
4909
|
-
]
|
|
4910
|
-
})]
|
|
4911
|
-
}),
|
|
4912
|
-
existing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4913
|
-
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeReadonly,
|
|
4914
|
-
children: tt("repo.existing")
|
|
4915
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4916
|
-
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeSource,
|
|
4917
|
-
children: entry.origin
|
|
4918
|
-
})
|
|
4919
|
-
]
|
|
4920
|
-
}, entry.path);
|
|
4921
|
-
})
|
|
4922
|
-
}),
|
|
4923
|
-
visibleCount < filtered.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4924
|
-
style: {
|
|
4925
|
-
textAlign: "center",
|
|
4926
|
-
marginTop: 8
|
|
4927
|
-
},
|
|
4928
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4929
|
-
type: "button",
|
|
4930
|
-
className: panel_module_css_default.button,
|
|
4931
|
-
onClick: () => setVisibleCount((n) => n + 50),
|
|
4932
|
-
children: [
|
|
4933
|
-
"加载更多 50 (剩余 ",
|
|
4934
|
-
filtered.length - visibleCount,
|
|
4935
|
-
")"
|
|
4936
|
-
]
|
|
4937
|
-
})
|
|
4938
|
-
}) : null,
|
|
4939
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4940
|
-
className: panel_module_css_default.buttons + " " + panel_module_css_default.actionsTop,
|
|
4941
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4942
|
-
type: "button",
|
|
4943
|
-
className: panel_module_css_default.button + " " + panel_module_css_default.primary,
|
|
4944
|
-
disabled: selected.length === 0 || repoImporting,
|
|
4945
|
-
onClick: () => {
|
|
4946
|
-
importRepo();
|
|
4947
|
-
},
|
|
4948
|
-
children: repoImporting ? tt("repo.importing") : `${tt("repo.import", { count: selected.length })} · ${formatBytes(selectedBytes)}`
|
|
4949
|
-
}), repoImporting ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4950
|
-
type: "button",
|
|
4951
|
-
className: panel_module_css_default.button,
|
|
4952
|
-
onClick: () => {
|
|
4953
|
-
cancelImport();
|
|
4954
|
-
},
|
|
4955
|
-
children: tt("repo.cancel")
|
|
4956
|
-
}) : null]
|
|
4957
|
-
}),
|
|
4958
|
-
repoResult !== null && repoResult.status === "running" ? (() => {
|
|
4959
|
-
const pct = repoResult.totalBytes > 0 ? Math.min(100, Math.round(repoResult.downloadedBytes / repoResult.totalBytes * 100)) : repoResult.total > 0 ? Math.round(repoResult.done / repoResult.total * 100) : 0;
|
|
4960
|
-
const speed = formatSpeed(repoResult.bytesPerSecond);
|
|
4961
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4962
|
-
className: panel_module_css_default.formSuccess + " " + panel_module_css_default.actionsTop,
|
|
4963
|
-
children: [
|
|
4964
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4965
|
-
style: {
|
|
4966
|
-
display: "flex",
|
|
4967
|
-
alignItems: "center",
|
|
4968
|
-
gap: 8,
|
|
4969
|
-
marginBottom: 6
|
|
4970
|
-
},
|
|
4971
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4972
|
-
className: panel_module_css_default.scanProgressTrack,
|
|
4973
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4974
|
-
className: panel_module_css_default.scanProgressFill,
|
|
4975
|
-
style: { width: `${pct}%` }
|
|
4976
|
-
})
|
|
4977
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4978
|
-
style: {
|
|
4979
|
-
fontSize: 11,
|
|
4980
|
-
opacity: .75,
|
|
4981
|
-
minWidth: 32,
|
|
4982
|
-
textAlign: "right"
|
|
4983
|
-
},
|
|
4984
|
-
children: [pct, "%"]
|
|
4985
|
-
})]
|
|
4986
|
-
}),
|
|
4987
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4988
|
-
className: panel_module_css_default.hintLine,
|
|
4989
|
-
children: [
|
|
4990
|
-
formatBytes(repoResult.downloadedBytes),
|
|
4991
|
-
" / ",
|
|
4992
|
-
formatBytes(repoResult.totalBytes),
|
|
4993
|
-
speed !== "" ? ` · ${speed}` : "",
|
|
4994
|
-
" · ",
|
|
4995
|
-
repoResult.done,
|
|
4996
|
-
"/",
|
|
4997
|
-
repoResult.total,
|
|
4998
|
-
" 个技能"
|
|
4999
|
-
]
|
|
5000
|
-
}),
|
|
5001
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5002
|
-
className: panel_module_css_default.hintLine,
|
|
5003
|
-
style: { marginTop: 4 },
|
|
5004
|
-
children: [repoResult.current !== void 0 ? tt("repo.importingCurrent", {
|
|
5005
|
-
name: repoResult.current,
|
|
5006
|
-
done: repoResult.done + 1,
|
|
5007
|
-
total: repoResult.total
|
|
5008
|
-
}) : tt("repo.importing"), repoResult.currentFile !== void 0 ? ` · ${repoResult.currentFile}` : ""]
|
|
5009
|
-
}),
|
|
5010
|
-
repoResult.failed.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5011
|
-
className: panel_module_css_default.errorBanner,
|
|
5012
|
-
style: { marginTop: 6 },
|
|
5013
|
-
children: repoResult.failed.map((f) => `${f.name}: ${f.error}`).join("; ")
|
|
5014
|
-
}) : null
|
|
5015
|
-
]
|
|
5016
|
-
});
|
|
5017
|
-
})() : null
|
|
5018
|
-
] })
|
|
5019
|
-
] });
|
|
5020
|
-
})() : null,
|
|
5021
|
-
repoResult !== null && repoResult.status !== "running" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5022
|
-
className: repoResult.status === "cancelled" ? panel_module_css_default.errorBanner + " " + panel_module_css_default.actionsTop : panel_module_css_default.formSuccess + " " + panel_module_css_default.actionsTop,
|
|
5023
|
-
children: [
|
|
5024
|
-
repoResult.status === "cancelled" ? tt("repo.cancelled", {
|
|
5025
|
-
imported: repoResult.imported.length,
|
|
5026
|
-
total: repoResult.total
|
|
5027
|
-
}) : `${tt("repo.imported", { count: repoResult.imported.length })} · ${tt("repo.skippedExisting", { count: repoResult.skipped.length })} · ${tt("repo.failed", { count: repoResult.failed.length })}`,
|
|
5028
|
-
repoResult.failed.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5029
|
-
style: { marginTop: 6 },
|
|
5030
|
-
children: repoResult.failed.map((f) => `${f.name}: ${f.error}`).join("; ")
|
|
5031
|
-
}) : null,
|
|
5032
|
-
repoResult.status === "error" && repoResult.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5033
|
-
style: { marginTop: 6 },
|
|
5034
|
-
children: repoResult.error
|
|
5035
|
-
}) : null
|
|
5036
|
-
]
|
|
5037
|
-
}) : null
|
|
5038
|
-
]
|
|
5039
|
-
})]
|
|
5040
|
-
}) : null,
|
|
5041
|
-
updateAllDialog ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmDialog, {
|
|
5042
|
-
title: tt("market.updateAllConfirmTitle"),
|
|
5043
|
-
text: tt("market.updateAllConfirmText"),
|
|
5044
|
-
items: updatableRepos.map(([repo, check]) => tt("market.updateAllItem", {
|
|
5045
|
-
repo,
|
|
5046
|
-
count: check.updated.length
|
|
5047
|
-
})),
|
|
5048
|
-
confirmLabel: tt("market.updateAll"),
|
|
5049
|
-
danger: true,
|
|
5050
|
-
onCancel: () => {
|
|
5051
|
-
setUpdateAllDialog(false);
|
|
5179
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RepoScanCard, { hub }),
|
|
5180
|
+
updateAllDialog ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmDialog, {
|
|
5181
|
+
title: tt("market.updateAllConfirmTitle"),
|
|
5182
|
+
text: tt("market.updateAllConfirmText"),
|
|
5183
|
+
items: updatableRepos.map(([repo, check]) => tt("market.updateAllItem", {
|
|
5184
|
+
repo,
|
|
5185
|
+
count: check.updated.length
|
|
5186
|
+
})),
|
|
5187
|
+
confirmLabel: tt("market.updateAll"),
|
|
5188
|
+
danger: true,
|
|
5189
|
+
onCancel: () => {
|
|
5190
|
+
setUpdateAllDialog(false);
|
|
5052
5191
|
},
|
|
5053
5192
|
onConfirm: () => {
|
|
5054
5193
|
updateAll();
|
|
@@ -5057,18 +5196,7 @@ window.__ModuleLoader__.load({
|
|
|
5057
5196
|
] });
|
|
5058
5197
|
}
|
|
5059
5198
|
//#endregion
|
|
5060
|
-
//#region src/client/panel/
|
|
5061
|
-
/**
|
|
5062
|
-
* useSkillHub — the panel's state owner. Everything the skill hub panel
|
|
5063
|
-
* knows (catalog, groups, sources, market, dialogs, drafts) plus every
|
|
5064
|
-
* async flow lives here; the panel itself only renders. All hooks run
|
|
5065
|
-
* unconditionally at the top, so the panel may early-return for the detail
|
|
5066
|
-
* and tag-editor views without violating the rules of hooks.
|
|
5067
|
-
*/
|
|
5068
|
-
/** Catalog poll interval while the panel is mounted (the provider watcher feeds this). */
|
|
5069
|
-
const POLL_MS = 5e3;
|
|
5070
|
-
/** Relaxed poll for slower-changing data (stats, groups, sources, config). */
|
|
5071
|
-
const SLOW_POLL_MS = 6e4;
|
|
5199
|
+
//#region src/client/panel/hooks/shared.ts
|
|
5072
5200
|
/** localStorage key of the last daily auto-check timestamp (survives sessions). */
|
|
5073
5201
|
const AUTO_CHECK_KEY = "skill-hub.last-auto-check";
|
|
5074
5202
|
/** Daily auto-check interval: at most one network round per day. */
|
|
@@ -5090,108 +5218,16 @@ window.__ModuleLoader__.load({
|
|
|
5090
5218
|
localStorage.setItem(AUTO_CHECK_KEY, String(Date.now()));
|
|
5091
5219
|
} catch {}
|
|
5092
5220
|
}
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5221
|
+
//#endregion
|
|
5222
|
+
//#region src/client/panel/hooks/useMetaFlow.ts
|
|
5223
|
+
/**
|
|
5224
|
+
* useMetaFlow — 自身更新检查 + 调用统计 + 配置:只依赖 api,
|
|
5225
|
+
* 不碰其他域。补捞 effect 自包含(uses.size + loadUses)。
|
|
5226
|
+
*/
|
|
5227
|
+
function useMetaFlow(api) {
|
|
5099
5228
|
const [updateState, setUpdateState] = (0, react.useState)({ status: "idle" });
|
|
5100
|
-
const [repoDiscoverState, setRepoDiscoverState] = (0, react.useState)({ status: "idle" });
|
|
5101
|
-
/** Market repo currently being scanned (null when idle); per-row busy state. */
|
|
5102
|
-
const [scanningRepo, setScanningRepo] = (0, react.useState)(null);
|
|
5103
|
-
const [repoSelected, setRepoSelected] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
5104
|
-
const [repoImporting, setRepoImporting] = (0, react.useState)(false);
|
|
5105
|
-
const [repoResult, setRepoResult] = (0, react.useState)(null);
|
|
5106
|
-
const [importJobId, setImportJobId] = (0, react.useState)(null);
|
|
5107
|
-
const pollAbortRef = (0, react.useRef)(null);
|
|
5108
|
-
const [search, setSearch] = (0, react.useState)("");
|
|
5109
|
-
/** 工作区(项目)路径;空 = 只看用户级技能。 */
|
|
5110
|
-
const [workspace, setWorkspace] = (0, react.useState)("");
|
|
5111
|
-
const [detail, setDetail] = (0, react.useState)(null);
|
|
5112
|
-
const [detailLoading, setDetailLoading] = (0, react.useState)(false);
|
|
5113
|
-
const [busyNames, setBusyNames] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
5114
|
-
const [batchBusy, setBatchBusy] = (0, react.useState)(false);
|
|
5115
|
-
const [showForm, setShowForm] = (0, react.useState)(false);
|
|
5116
|
-
const [formName, setFormName] = (0, react.useState)("");
|
|
5117
|
-
const [formDesc, setFormDesc] = (0, react.useState)("");
|
|
5118
|
-
const [formRoot, setFormRoot] = (0, react.useState)("user-dsh");
|
|
5119
|
-
const [formBusy, setFormBusy] = (0, react.useState)(false);
|
|
5120
|
-
const [formMessage, setFormMessage] = (0, react.useState)(null);
|
|
5121
5229
|
const [uses, setUses] = (0, react.useState)(/* @__PURE__ */ new Map());
|
|
5122
5230
|
const [hubConfig, setHubConfig] = (0, react.useState)(null);
|
|
5123
|
-
const [tab, setTab] = (0, react.useState)("sources");
|
|
5124
|
-
const [skillView, setSkillView] = (0, react.useState)("groups");
|
|
5125
|
-
const [sourceFilter, setSourceFilter] = (0, react.useState)("all");
|
|
5126
|
-
const [invocationFilter, setInvocationFilter] = (0, react.useState)("all");
|
|
5127
|
-
const [sortKey, setSortKey] = (0, react.useState)("name");
|
|
5128
|
-
const [marketState, setMarketState] = (0, react.useState)({
|
|
5129
|
-
status: "loading",
|
|
5130
|
-
repos: []
|
|
5131
|
-
});
|
|
5132
|
-
const [marketCheck, setMarketCheck] = (0, react.useState)({});
|
|
5133
|
-
const [branchChoice, setBranchChoice] = (0, react.useState)(null);
|
|
5134
|
-
const [branchBusy, setBranchBusy] = (0, react.useState)(false);
|
|
5135
|
-
const [marketSyncDialog, setMarketSyncDialog] = (0, react.useState)(null);
|
|
5136
|
-
const [syncingMarket, setSyncingMarket] = (0, react.useState)(null);
|
|
5137
|
-
const [syncBusy, setSyncBusy] = (0, react.useState)(false);
|
|
5138
|
-
const [newSourceName, setNewSourceName] = (0, react.useState)("");
|
|
5139
|
-
const [groupsState, setGroupsState] = (0, react.useState)(null);
|
|
5140
|
-
const [sourcesState, setSourcesState] = (0, react.useState)(null);
|
|
5141
|
-
const [sourceCheck, setSourceCheck] = (0, react.useState)({});
|
|
5142
|
-
const [checkingSource, setCheckingSource] = (0, react.useState)(null);
|
|
5143
|
-
const [syncingSource, setSyncingSource] = (0, react.useState)(null);
|
|
5144
|
-
const [conflictDialog, setConflictDialog] = (0, react.useState)(null);
|
|
5145
|
-
const [confirmDialog, setConfirmDialog] = (0, react.useState)(null);
|
|
5146
|
-
const [deleteSkillDialog, setDeleteSkillDialog] = (0, react.useState)(null);
|
|
5147
|
-
const [deleteGroupDialog, setDeleteGroupDialog] = (0, react.useState)(null);
|
|
5148
|
-
const [confirmClearTrash, setConfirmClearTrash] = (0, react.useState)(false);
|
|
5149
|
-
/** 「全部更新」确认对话框(市场 tab)。 */
|
|
5150
|
-
const [updateAllDialog, setUpdateAllDialog] = (0, react.useState)(false);
|
|
5151
|
-
const [editingTag, setEditingTag] = (0, react.useState)(null);
|
|
5152
|
-
const [editName, setEditName] = (0, react.useState)("");
|
|
5153
|
-
const [membersDraft, setMembersDraft] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
5154
|
-
const [newTagName, setNewTagName] = (0, react.useState)("");
|
|
5155
|
-
const [tagBusy, setTagBusy] = (0, react.useState)(false);
|
|
5156
|
-
const [editSearch, setEditSearch] = (0, react.useState)("");
|
|
5157
|
-
/** 分组视图里收起的分组(key 为 tag:<id>、col:<name> 或 project 树键)。技能总数 >80 时首次加载自动折叠 personal。 */
|
|
5158
|
-
const [collapsedGroups, setCollapsedGroups] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
5159
|
-
const autoCollapsedPersonal = (0, react.useRef)(false);
|
|
5160
|
-
/** 项目级三级树里已细分(按 .dsh/.agents)的项目键。 */
|
|
5161
|
-
const [subdividedProjects, setSubdividedProjects] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
5162
|
-
const [showLegend, setShowLegend] = (0, react.useState)(false);
|
|
5163
|
-
const [editMode, setEditMode] = (0, react.useState)(false);
|
|
5164
|
-
const toggleGroupCollapse = (0, react.useCallback)((key) => {
|
|
5165
|
-
setCollapsedGroups((previous) => {
|
|
5166
|
-
const next = new Set(previous);
|
|
5167
|
-
if (next.has(key)) next.delete(key);
|
|
5168
|
-
else next.add(key);
|
|
5169
|
-
return next;
|
|
5170
|
-
});
|
|
5171
|
-
}, []);
|
|
5172
|
-
const toggleSubdivide = (0, react.useCallback)((key) => {
|
|
5173
|
-
setSubdividedProjects((previous) => {
|
|
5174
|
-
const next = new Set(previous);
|
|
5175
|
-
if (next.has(key)) next.delete(key);
|
|
5176
|
-
else next.add(key);
|
|
5177
|
-
return next;
|
|
5178
|
-
});
|
|
5179
|
-
}, []);
|
|
5180
|
-
const load = (0, react.useCallback)(async () => {
|
|
5181
|
-
try {
|
|
5182
|
-
const next = await api.catalog(workspace !== "" ? { cwd: workspace } : void 0);
|
|
5183
|
-
setCatalog(next);
|
|
5184
|
-
if (!autoCollapsedPersonal.current && next.skills.length + next.disabled.length > 80) {
|
|
5185
|
-
autoCollapsedPersonal.current = true;
|
|
5186
|
-
setCollapsedGroups((previous) => new Set(previous).add("uncategorized-source"));
|
|
5187
|
-
}
|
|
5188
|
-
setLoadError(null);
|
|
5189
|
-
} catch (error) {
|
|
5190
|
-
setLoadError(errorMessage(error));
|
|
5191
|
-
} finally {
|
|
5192
|
-
setLoading(false);
|
|
5193
|
-
}
|
|
5194
|
-
}, [api, workspace]);
|
|
5195
5231
|
const loadUses = (0, react.useCallback)(async () => {
|
|
5196
5232
|
try {
|
|
5197
5233
|
const result = await api.stats();
|
|
@@ -5206,7 +5242,6 @@ window.__ModuleLoader__.load({
|
|
|
5206
5242
|
setHubConfig((await api.config()).config);
|
|
5207
5243
|
} catch {}
|
|
5208
5244
|
}, [api]);
|
|
5209
|
-
/** Check the hub's own GitHub release once, and again on demand. */
|
|
5210
5245
|
const checkUpdate = (0, react.useCallback)(async () => {
|
|
5211
5246
|
setUpdateState({ status: "checking" });
|
|
5212
5247
|
try {
|
|
@@ -5221,95 +5256,96 @@ window.__ModuleLoader__.load({
|
|
|
5221
5256
|
});
|
|
5222
5257
|
}
|
|
5223
5258
|
}, [api]);
|
|
5224
|
-
|
|
5225
|
-
const loadGroups = (0, react.useCallback)(async () => {
|
|
5226
|
-
try {
|
|
5227
|
-
setGroupsState(await api.groups());
|
|
5228
|
-
} catch (error) {
|
|
5229
|
-
setLoadError(errorMessage(error));
|
|
5230
|
-
}
|
|
5231
|
-
}, [api]);
|
|
5232
|
-
/** 加载来源记录 + 回收站。 */
|
|
5233
|
-
const loadSources = (0, react.useCallback)(async () => {
|
|
5234
|
-
try {
|
|
5235
|
-
setSourcesState(await api.sources());
|
|
5236
|
-
} catch (error) {
|
|
5237
|
-
setLoadError(errorMessage(error));
|
|
5238
|
-
}
|
|
5239
|
-
}, [api]);
|
|
5240
|
-
/** 加载市场源列表。 */
|
|
5241
|
-
const loadMarket = (0, react.useCallback)(async () => {
|
|
5242
|
-
try {
|
|
5243
|
-
const next = await api.market();
|
|
5244
|
-
setMarketState({
|
|
5245
|
-
status: "ready",
|
|
5246
|
-
repos: next.repos
|
|
5247
|
-
});
|
|
5248
|
-
} catch (error) {
|
|
5249
|
-
setMarketState({
|
|
5250
|
-
status: "error",
|
|
5251
|
-
repos: []
|
|
5252
|
-
});
|
|
5253
|
-
setLoadError(errorMessage(error));
|
|
5254
|
-
}
|
|
5255
|
-
}, [api]);
|
|
5256
|
-
/** 把最新的 tag 列表合并进 groups 状态(tags 以外的字段保持原样)。 */
|
|
5257
|
-
const applyTags = (0, react.useCallback)((tags) => {
|
|
5258
|
-
setGroupsState((previous) => previous === null ? {
|
|
5259
|
-
ok: true,
|
|
5260
|
-
tags,
|
|
5261
|
-
collections: [],
|
|
5262
|
-
origins: {}
|
|
5263
|
-
} : {
|
|
5264
|
-
...previous,
|
|
5265
|
-
tags
|
|
5266
|
-
});
|
|
5267
|
-
}, []);
|
|
5259
|
+
const usesCatchUp = (0, react.useRef)(0);
|
|
5268
5260
|
(0, react.useEffect)(() => {
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
loadSources();
|
|
5273
|
-
loadConfig();
|
|
5274
|
-
const fast = window.setInterval(() => {
|
|
5275
|
-
load();
|
|
5276
|
-
}, POLL_MS);
|
|
5277
|
-
const slow = window.setInterval(() => {
|
|
5261
|
+
if (uses.size > 0 || usesCatchUp.current >= 72) return;
|
|
5262
|
+
const timer = window.setInterval(() => {
|
|
5263
|
+
usesCatchUp.current += 1;
|
|
5278
5264
|
loadUses();
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
loadConfig();
|
|
5282
|
-
}, SLOW_POLL_MS);
|
|
5265
|
+
if (usesCatchUp.current >= 72) window.clearInterval(timer);
|
|
5266
|
+
}, 5e3);
|
|
5283
5267
|
return () => {
|
|
5284
|
-
window.clearInterval(
|
|
5285
|
-
window.clearInterval(slow);
|
|
5268
|
+
window.clearInterval(timer);
|
|
5286
5269
|
};
|
|
5287
|
-
}, [
|
|
5288
|
-
|
|
5270
|
+
}, [uses.size, loadUses]);
|
|
5271
|
+
return {
|
|
5272
|
+
updateState,
|
|
5273
|
+
checkUpdate,
|
|
5274
|
+
uses,
|
|
5289
5275
|
loadUses,
|
|
5290
|
-
|
|
5291
|
-
loadSources,
|
|
5276
|
+
hubConfig,
|
|
5292
5277
|
loadConfig
|
|
5278
|
+
};
|
|
5279
|
+
}
|
|
5280
|
+
//#endregion
|
|
5281
|
+
//#region src/client/panel/hooks/useCatalogFlow.ts
|
|
5282
|
+
/**
|
|
5283
|
+
* useCatalogFlow — 目录域:catalog 加载、详情、开关(单个/禁用态/批量)、
|
|
5284
|
+
* 新建、诊断修复,以及列表过滤/排序派生。跨域刷新只通过 shared 通知,
|
|
5285
|
+
* 不直接碰其他域的 state。
|
|
5286
|
+
*/
|
|
5287
|
+
function useCatalogFlow(api, uses, shared, collapsePersonal, clearSourceFilter) {
|
|
5288
|
+
const [catalog, setCatalog] = (0, react.useState)(null);
|
|
5289
|
+
const [loading, setLoading] = (0, react.useState)(true);
|
|
5290
|
+
const [detail, setDetail] = (0, react.useState)(null);
|
|
5291
|
+
const [detailLoading, setDetailLoading] = (0, react.useState)(false);
|
|
5292
|
+
const [busyNames, setBusyNames] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
5293
|
+
const [search, setSearch] = (0, react.useState)("");
|
|
5294
|
+
/** 工作区(项目)路径;空 = 只看用户级技能。 */
|
|
5295
|
+
const [workspace, setWorkspace] = (0, react.useState)("");
|
|
5296
|
+
const [showForm, setShowForm] = (0, react.useState)(false);
|
|
5297
|
+
const [formName, setFormName] = (0, react.useState)("");
|
|
5298
|
+
const [formDesc, setFormDesc] = (0, react.useState)("");
|
|
5299
|
+
const [formRoot, setFormRoot] = (0, react.useState)("user-dsh");
|
|
5300
|
+
const [formBusy, setFormBusy] = (0, react.useState)(false);
|
|
5301
|
+
const [formMessage, setFormMessage] = (0, react.useState)(null);
|
|
5302
|
+
const [fixingPaths, setFixingPaths] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
5303
|
+
const [invocationFilter, setInvocationFilter] = (0, react.useState)("all");
|
|
5304
|
+
const [sortKey, setSortKey] = (0, react.useState)("name");
|
|
5305
|
+
const autoCollapsedPersonal = (0, react.useRef)(false);
|
|
5306
|
+
const load = (0, react.useCallback)(async () => {
|
|
5307
|
+
try {
|
|
5308
|
+
const next = await api.catalog(workspace !== "" ? { cwd: workspace } : void 0);
|
|
5309
|
+
setCatalog(next);
|
|
5310
|
+
if (!autoCollapsedPersonal.current && next.skills.length + next.disabled.length > 80) {
|
|
5311
|
+
autoCollapsedPersonal.current = true;
|
|
5312
|
+
collapsePersonal();
|
|
5313
|
+
}
|
|
5314
|
+
shared.clearFail();
|
|
5315
|
+
} catch (error) {
|
|
5316
|
+
shared.fail(errorMessage(error));
|
|
5317
|
+
} finally {
|
|
5318
|
+
setLoading(false);
|
|
5319
|
+
}
|
|
5320
|
+
}, [
|
|
5321
|
+
api,
|
|
5322
|
+
workspace,
|
|
5323
|
+
shared,
|
|
5324
|
+
collapsePersonal
|
|
5293
5325
|
]);
|
|
5294
5326
|
const openDetail = (0, react.useCallback)(async (name) => {
|
|
5295
5327
|
setDetailLoading(true);
|
|
5296
|
-
|
|
5328
|
+
shared.clearFail();
|
|
5297
5329
|
try {
|
|
5298
5330
|
setDetail(await api.skill(name, workspace !== "" ? { cwd: workspace } : void 0));
|
|
5299
5331
|
} catch (error) {
|
|
5300
|
-
|
|
5332
|
+
shared.fail(errorMessage(error));
|
|
5301
5333
|
} finally {
|
|
5302
5334
|
setDetailLoading(false);
|
|
5303
5335
|
}
|
|
5304
|
-
}, [
|
|
5336
|
+
}, [
|
|
5337
|
+
api,
|
|
5338
|
+
workspace,
|
|
5339
|
+
shared
|
|
5340
|
+
]);
|
|
5305
5341
|
const toggle = (0, react.useCallback)(async (skill, enabled) => {
|
|
5306
5342
|
setBusyNames((previous) => new Set(previous).add(skill.name));
|
|
5307
|
-
|
|
5343
|
+
shared.clearFail();
|
|
5308
5344
|
try {
|
|
5309
5345
|
const next = await api.toggle(skill.name, enabled);
|
|
5310
5346
|
setCatalog(next);
|
|
5311
5347
|
} catch (error) {
|
|
5312
|
-
|
|
5348
|
+
shared.fail(errorMessage(error));
|
|
5313
5349
|
} finally {
|
|
5314
5350
|
setBusyNames((previous) => {
|
|
5315
5351
|
const next = new Set(previous);
|
|
@@ -5317,15 +5353,15 @@ window.__ModuleLoader__.load({
|
|
|
5317
5353
|
return next;
|
|
5318
5354
|
});
|
|
5319
5355
|
}
|
|
5320
|
-
}, [api]);
|
|
5356
|
+
}, [api, shared]);
|
|
5321
5357
|
const enableDisabled = (0, react.useCallback)(async (record) => {
|
|
5322
5358
|
setBusyNames((previous) => new Set(previous).add(record.name));
|
|
5323
|
-
|
|
5359
|
+
shared.clearFail();
|
|
5324
5360
|
try {
|
|
5325
5361
|
const next = await api.toggle(record.name, true);
|
|
5326
5362
|
setCatalog(next);
|
|
5327
5363
|
} catch (error) {
|
|
5328
|
-
|
|
5364
|
+
shared.fail(errorMessage(error));
|
|
5329
5365
|
} finally {
|
|
5330
5366
|
setBusyNames((previous) => {
|
|
5331
5367
|
const next = new Set(previous);
|
|
@@ -5333,412 +5369,659 @@ window.__ModuleLoader__.load({
|
|
|
5333
5369
|
return next;
|
|
5334
5370
|
});
|
|
5335
5371
|
}
|
|
5336
|
-
}, [api]);
|
|
5372
|
+
}, [api, shared]);
|
|
5337
5373
|
/** Toggle an explicit name set in one write (enables disabled members too). */
|
|
5338
5374
|
const batchToggleNames = (0, react.useCallback)(async (names, enabled) => {
|
|
5339
5375
|
if (names.length === 0) return;
|
|
5340
|
-
setBatchBusy(true);
|
|
5341
|
-
|
|
5376
|
+
shared.setBatchBusy(true);
|
|
5377
|
+
shared.clearFail();
|
|
5342
5378
|
try {
|
|
5343
5379
|
const next = await api.toggleBatch(names, enabled);
|
|
5344
5380
|
setCatalog(next.catalog);
|
|
5345
|
-
if (next.failures.length > 0)
|
|
5381
|
+
if (next.failures.length > 0) shared.fail("toggle-batch: " + next.failures.map((failure) => failure.name + ": " + failure.error).join("; "));
|
|
5346
5382
|
} catch (error) {
|
|
5347
|
-
|
|
5383
|
+
shared.fail(errorMessage(error));
|
|
5348
5384
|
} finally {
|
|
5349
|
-
setBatchBusy(false);
|
|
5385
|
+
shared.setBatchBusy(false);
|
|
5350
5386
|
}
|
|
5351
|
-
}, [api]);
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
const members = groupMap().get(key) ?? [];
|
|
5369
|
-
if (members.length === 0) return;
|
|
5370
|
-
if (view === "off") {
|
|
5371
|
-
batchToggleNames(members, true);
|
|
5372
|
-
return;
|
|
5387
|
+
}, [api, shared]);
|
|
5388
|
+
const fixDiagnostic = (0, react.useCallback)(async (path) => {
|
|
5389
|
+
setFixingPaths((previous) => new Set(previous).add(path));
|
|
5390
|
+
shared.clearFail();
|
|
5391
|
+
try {
|
|
5392
|
+
if (!window.confirm(tt("diag.fixConfirm", { path }))) return;
|
|
5393
|
+
await api.fixDiagnostic(path);
|
|
5394
|
+
await load();
|
|
5395
|
+
shared.succeed(tt("diag.fixed"));
|
|
5396
|
+
} catch (error) {
|
|
5397
|
+
shared.fail(errorMessage(error));
|
|
5398
|
+
} finally {
|
|
5399
|
+
setFixingPaths((previous) => {
|
|
5400
|
+
const next = new Set(previous);
|
|
5401
|
+
next.delete(path);
|
|
5402
|
+
return next;
|
|
5403
|
+
});
|
|
5373
5404
|
}
|
|
5374
|
-
const others = [...groupMap().entries()].filter(([otherKey]) => otherKey !== key).map(([, memberNames]) => ({ members: memberNames }));
|
|
5375
|
-
const conflicts = conflictsOnClose(members, actionNames, others);
|
|
5376
|
-
if (conflicts.length > 0) setConflictDialog({
|
|
5377
|
-
key,
|
|
5378
|
-
name,
|
|
5379
|
-
conflicts
|
|
5380
|
-
});
|
|
5381
|
-
else batchToggleNames(members.filter((member) => actionNames.has(member)), false);
|
|
5382
5405
|
}, [
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5406
|
+
api,
|
|
5407
|
+
load,
|
|
5408
|
+
shared
|
|
5386
5409
|
]);
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5410
|
+
const create = (0, react.useCallback)(async (event) => {
|
|
5411
|
+
event.preventDefault();
|
|
5412
|
+
setFormBusy(true);
|
|
5413
|
+
setFormMessage(null);
|
|
5414
|
+
shared.succeed(null);
|
|
5415
|
+
try {
|
|
5416
|
+
const result = await api.create({
|
|
5417
|
+
name: formName,
|
|
5418
|
+
description: formDesc,
|
|
5419
|
+
root: formRoot
|
|
5420
|
+
});
|
|
5421
|
+
shared.succeed(tt("form.success") + result.path);
|
|
5422
|
+
setFormName("");
|
|
5423
|
+
setFormDesc("");
|
|
5424
|
+
setShowForm(false);
|
|
5425
|
+
await load();
|
|
5426
|
+
} catch (error) {
|
|
5427
|
+
setFormMessage({
|
|
5428
|
+
kind: "error",
|
|
5429
|
+
text: tt("form.error") + errorMessage(error)
|
|
5430
|
+
});
|
|
5431
|
+
} finally {
|
|
5432
|
+
setFormBusy(false);
|
|
5433
|
+
}
|
|
5395
5434
|
}, [
|
|
5435
|
+
api,
|
|
5436
|
+
formName,
|
|
5437
|
+
formDesc,
|
|
5438
|
+
formRoot,
|
|
5439
|
+
load,
|
|
5440
|
+
shared
|
|
5441
|
+
]);
|
|
5442
|
+
const normalized = search.trim().toLocaleLowerCase();
|
|
5443
|
+
/** 当前启用且可写的技能名(组开关只作用于它们)。 */
|
|
5444
|
+
const actionNames = (0, react.useMemo)(() => new Set((catalog?.skills ?? []).filter((skill) => skill.writable).map((skill) => skill.name)), [catalog]);
|
|
5445
|
+
/** 当前启用的全部技能名(含只读,用于派生开关状态)。 */
|
|
5446
|
+
const viewNames = (0, react.useMemo)(() => new Set((catalog?.skills ?? []).map((skill) => skill.name)), [catalog]);
|
|
5447
|
+
const filtered = (0, react.useMemo)(() => (catalog?.skills ?? []).filter((skill) => {
|
|
5448
|
+
if (invocationFilter === "model" && !skill.invocation.modelInvocable) return false;
|
|
5449
|
+
if (invocationFilter === "user" && !skill.invocation.userInvocable) return false;
|
|
5450
|
+
if (normalized.length === 0) return true;
|
|
5451
|
+
return skill.name.toLocaleLowerCase().includes(normalized) || skill.description.toLocaleLowerCase().includes(normalized) || skill.displayName?.toLocaleLowerCase().includes(normalized) || skill.shortDescription?.toLocaleLowerCase().includes(normalized);
|
|
5452
|
+
}), [
|
|
5453
|
+
catalog,
|
|
5454
|
+
normalized,
|
|
5455
|
+
invocationFilter
|
|
5456
|
+
]);
|
|
5457
|
+
/** Rows actually rendered with a shortened description (shortDescription in use). */
|
|
5458
|
+
const shortenedCount = (0, react.useMemo)(() => filtered.filter((skill) => skill.shortDescription !== void 0).length, [filtered]);
|
|
5459
|
+
return {
|
|
5460
|
+
catalog,
|
|
5461
|
+
loading,
|
|
5462
|
+
detail,
|
|
5463
|
+
detailLoading,
|
|
5464
|
+
busyNames,
|
|
5465
|
+
search,
|
|
5466
|
+
workspace,
|
|
5467
|
+
showForm,
|
|
5468
|
+
formName,
|
|
5469
|
+
formDesc,
|
|
5470
|
+
formRoot,
|
|
5471
|
+
formBusy,
|
|
5472
|
+
formMessage,
|
|
5473
|
+
fixingPaths,
|
|
5474
|
+
invocationFilter,
|
|
5475
|
+
sortKey,
|
|
5476
|
+
normalized,
|
|
5477
|
+
actionNames,
|
|
5478
|
+
viewNames,
|
|
5479
|
+
filtered,
|
|
5480
|
+
sorted: (0, react.useMemo)(() => sortSkills(filtered, sortKey, (name) => uses.get(name)?.count), [
|
|
5481
|
+
filtered,
|
|
5482
|
+
sortKey,
|
|
5483
|
+
uses
|
|
5484
|
+
]),
|
|
5485
|
+
shortenedCount,
|
|
5486
|
+
setDetail,
|
|
5487
|
+
setSearch,
|
|
5488
|
+
setWorkspace,
|
|
5489
|
+
setShowForm,
|
|
5490
|
+
setFormName,
|
|
5491
|
+
setFormDesc,
|
|
5492
|
+
setFormRoot,
|
|
5493
|
+
setFormMessage,
|
|
5494
|
+
setInvocationFilter,
|
|
5495
|
+
setSortKey,
|
|
5496
|
+
load,
|
|
5497
|
+
openDetail,
|
|
5498
|
+
toggle,
|
|
5499
|
+
enableDisabled,
|
|
5500
|
+
batchToggleNames,
|
|
5501
|
+
fixDiagnostic,
|
|
5502
|
+
create,
|
|
5503
|
+
clearListFilters: (0, react.useCallback)(() => {
|
|
5504
|
+
setSearch("");
|
|
5505
|
+
clearSourceFilter();
|
|
5506
|
+
setInvocationFilter("all");
|
|
5507
|
+
}, [clearSourceFilter])
|
|
5508
|
+
};
|
|
5509
|
+
}
|
|
5510
|
+
//#endregion
|
|
5511
|
+
//#region src/client/panel/hooks/useGroupFlow.ts
|
|
5512
|
+
/**
|
|
5513
|
+
* useGroupFlow — 分组域:groups 加载、tag 新建/删除/成员/重排、集合重排、
|
|
5514
|
+
* 来源顶层重排、组开关与冲突解决。目录侧只消费 batchToggleNames +
|
|
5515
|
+
* actionNames(聚合根传入),不碰目录 state。
|
|
5516
|
+
*/
|
|
5517
|
+
function useGroupFlow(api, shared, batchToggleNames, actionNames) {
|
|
5518
|
+
const [groupsState, setGroupsState] = (0, react.useState)(null);
|
|
5519
|
+
const [conflictDialog, setConflictDialog] = (0, react.useState)(null);
|
|
5520
|
+
const [editingTag, setEditingTag] = (0, react.useState)(null);
|
|
5521
|
+
const [editName, setEditName] = (0, react.useState)("");
|
|
5522
|
+
const [membersDraft, setMembersDraft] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
5523
|
+
const [newTagName, setNewTagName] = (0, react.useState)("");
|
|
5524
|
+
const [editSearch, setEditSearch] = (0, react.useState)("");
|
|
5525
|
+
/** 加载用户 tag 分组 + 系统集合组 + origin 映射。 */
|
|
5526
|
+
const loadGroups = (0, react.useCallback)(async () => {
|
|
5527
|
+
try {
|
|
5528
|
+
setGroupsState(await api.groups());
|
|
5529
|
+
} catch (error) {
|
|
5530
|
+
shared.fail(errorMessage(error));
|
|
5531
|
+
}
|
|
5532
|
+
}, [api, shared]);
|
|
5533
|
+
/** 把最新的 tag 列表合并进 groups 状态(tags 以外的字段保持原样)。 */
|
|
5534
|
+
const applyTags = (0, react.useCallback)((tags) => {
|
|
5535
|
+
setGroupsState((previous) => previous === null ? {
|
|
5536
|
+
ok: true,
|
|
5537
|
+
tags,
|
|
5538
|
+
collections: [],
|
|
5539
|
+
origins: {}
|
|
5540
|
+
} : {
|
|
5541
|
+
...previous,
|
|
5542
|
+
tags
|
|
5543
|
+
});
|
|
5544
|
+
}, []);
|
|
5545
|
+
/** 所有分组:key → 成员名(tag 与来源集合)。 */
|
|
5546
|
+
const groupMap = (0, react.useCallback)(() => {
|
|
5547
|
+
const map = /* @__PURE__ */ new Map();
|
|
5548
|
+
for (const tag of groupsState?.tags ?? []) map.set("tag:" + tag.id, tag.skillNames);
|
|
5549
|
+
for (const collection of groupsState?.collections ?? []) map.set("col:" + collection.name, collection.skillNames);
|
|
5550
|
+
return map;
|
|
5551
|
+
}, [groupsState]);
|
|
5552
|
+
return {
|
|
5553
|
+
groupsState,
|
|
5396
5554
|
conflictDialog,
|
|
5555
|
+
editingTag,
|
|
5556
|
+
editName,
|
|
5557
|
+
membersDraft,
|
|
5558
|
+
newTagName,
|
|
5559
|
+
editSearch,
|
|
5560
|
+
setConflictDialog,
|
|
5561
|
+
setEditingTag,
|
|
5562
|
+
setEditName,
|
|
5563
|
+
setMembersDraft,
|
|
5564
|
+
setNewTagName,
|
|
5565
|
+
setEditSearch,
|
|
5566
|
+
loadGroups,
|
|
5567
|
+
applyTags,
|
|
5397
5568
|
groupMap,
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5569
|
+
toggleGroup: (0, react.useCallback)((key, name, view) => {
|
|
5570
|
+
const members = groupMap().get(key) ?? [];
|
|
5571
|
+
if (members.length === 0) return;
|
|
5572
|
+
if (view === "off") {
|
|
5573
|
+
batchToggleNames(members, true);
|
|
5574
|
+
return;
|
|
5575
|
+
}
|
|
5576
|
+
const conflicts = conflictsOnClose(members, actionNames, [...groupMap().entries()].filter(([otherKey]) => otherKey !== key).map(([, memberNames]) => ({ members: memberNames })));
|
|
5577
|
+
if (conflicts.length > 0) setConflictDialog({
|
|
5578
|
+
key,
|
|
5579
|
+
name,
|
|
5580
|
+
conflicts
|
|
5581
|
+
});
|
|
5582
|
+
else batchToggleNames(members.filter((member) => actionNames.has(member)), false);
|
|
5583
|
+
}, [
|
|
5584
|
+
groupMap,
|
|
5585
|
+
actionNames,
|
|
5586
|
+
batchToggleNames
|
|
5587
|
+
]),
|
|
5588
|
+
resolveConflict: (0, react.useCallback)(async (closeAll) => {
|
|
5589
|
+
const dialog = conflictDialog;
|
|
5590
|
+
if (dialog === null) return;
|
|
5591
|
+
setConflictDialog(null);
|
|
5592
|
+
const members = groupMap().get(dialog.key) ?? [];
|
|
5593
|
+
if (closeAll) await batchToggleNames(members.filter((member) => actionNames.has(member)), false);
|
|
5594
|
+
else await batchToggleNames(members.filter((member) => actionNames.has(member) && !dialog.conflicts.includes(member)), false);
|
|
5595
|
+
}, [
|
|
5596
|
+
conflictDialog,
|
|
5597
|
+
groupMap,
|
|
5598
|
+
actionNames,
|
|
5599
|
+
batchToggleNames
|
|
5600
|
+
]),
|
|
5601
|
+
createTag: (0, react.useCallback)(async (event) => {
|
|
5602
|
+
event.preventDefault();
|
|
5603
|
+
const name = newTagName.trim();
|
|
5604
|
+
if (name === "") return;
|
|
5605
|
+
shared.setTagBusy(true);
|
|
5606
|
+
shared.clearFail();
|
|
5607
|
+
try {
|
|
5608
|
+
applyTags(await api.saveTag({ name }));
|
|
5609
|
+
setNewTagName("");
|
|
5610
|
+
} catch (error) {
|
|
5611
|
+
shared.fail(errorMessage(error));
|
|
5612
|
+
} finally {
|
|
5613
|
+
shared.setTagBusy(false);
|
|
5614
|
+
}
|
|
5615
|
+
}, [
|
|
5616
|
+
api,
|
|
5617
|
+
applyTags,
|
|
5618
|
+
newTagName,
|
|
5619
|
+
shared
|
|
5620
|
+
]),
|
|
5621
|
+
deleteTag: (0, react.useCallback)(async (id) => {
|
|
5622
|
+
shared.setTagBusy(true);
|
|
5623
|
+
shared.clearFail();
|
|
5624
|
+
try {
|
|
5625
|
+
applyTags(await api.deleteTag(id));
|
|
5626
|
+
setEditingTag(null);
|
|
5627
|
+
} catch (error) {
|
|
5628
|
+
shared.fail(errorMessage(error));
|
|
5629
|
+
} finally {
|
|
5630
|
+
shared.setTagBusy(false);
|
|
5631
|
+
}
|
|
5632
|
+
}, [
|
|
5633
|
+
api,
|
|
5634
|
+
applyTags,
|
|
5635
|
+
shared
|
|
5636
|
+
]),
|
|
5637
|
+
saveTag: (0, react.useCallback)(async (id, name, memberNames) => {
|
|
5638
|
+
shared.setTagBusy(true);
|
|
5639
|
+
shared.clearFail();
|
|
5640
|
+
try {
|
|
5641
|
+
const safeName = name.trim();
|
|
5642
|
+
let tags = null;
|
|
5643
|
+
if (safeName !== "") tags = await api.saveTag({
|
|
5644
|
+
id,
|
|
5645
|
+
name: safeName
|
|
5646
|
+
});
|
|
5647
|
+
if (memberNames !== null) tags = await api.setTagMembers(id, memberNames);
|
|
5648
|
+
if (tags === null) return;
|
|
5649
|
+
applyTags(tags);
|
|
5650
|
+
setEditingTag(null);
|
|
5651
|
+
} catch (error) {
|
|
5652
|
+
shared.fail(errorMessage(error));
|
|
5653
|
+
} finally {
|
|
5654
|
+
shared.setTagBusy(false);
|
|
5655
|
+
}
|
|
5656
|
+
}, [
|
|
5657
|
+
api,
|
|
5658
|
+
applyTags,
|
|
5659
|
+
shared
|
|
5660
|
+
]),
|
|
5661
|
+
reorderTags: (0, react.useCallback)(async (orderedIds) => {
|
|
5662
|
+
shared.setTagBusy(true);
|
|
5663
|
+
shared.clearFail();
|
|
5664
|
+
try {
|
|
5665
|
+
const tags = await api.reorderTags(orderedIds);
|
|
5666
|
+
applyTags(tags);
|
|
5667
|
+
} catch (error) {
|
|
5668
|
+
const msg = errorMessage(error);
|
|
5669
|
+
if (msg.includes("404") || msg.toLowerCase().includes("not found")) {
|
|
5670
|
+
const byId = new Map(groupsState?.tags.map((t) => [t.id, t]) ?? []);
|
|
5671
|
+
const reordered = orderedIds.map((id) => byId.get(id)).filter((t) => t !== void 0);
|
|
5672
|
+
if (reordered.length === orderedIds.length) {
|
|
5673
|
+
applyTags(reordered);
|
|
5674
|
+
shared.succeed("已临时调整顺序(本地生效,重启宿主后持久化)");
|
|
5675
|
+
} else shared.fail(msg);
|
|
5676
|
+
} else shared.fail(msg);
|
|
5677
|
+
} finally {
|
|
5678
|
+
shared.setTagBusy(false);
|
|
5679
|
+
}
|
|
5680
|
+
}, [
|
|
5681
|
+
api,
|
|
5682
|
+
applyTags,
|
|
5683
|
+
groupsState,
|
|
5684
|
+
shared
|
|
5685
|
+
]),
|
|
5686
|
+
reorderCollections: (0, react.useCallback)(async (orderedNames) => {
|
|
5687
|
+
shared.setTagBusy(true);
|
|
5688
|
+
shared.clearFail();
|
|
5689
|
+
try {
|
|
5690
|
+
const collections = await api.reorderCollections(orderedNames);
|
|
5691
|
+
setGroupsState((prev) => prev === null ? prev : {
|
|
5692
|
+
...prev,
|
|
5693
|
+
collections
|
|
5694
|
+
});
|
|
5695
|
+
loadGroups();
|
|
5696
|
+
} catch (error) {
|
|
5697
|
+
const msg = errorMessage(error);
|
|
5698
|
+
if (msg.includes("404") || msg.toLowerCase().includes("not found")) {
|
|
5699
|
+
setGroupsState((prev) => {
|
|
5700
|
+
if (prev === null) return prev;
|
|
5701
|
+
const map = new Map(prev.collections.map((c) => [c.name, c]));
|
|
5702
|
+
const reordered = orderedNames.map((n) => map.get(n)).filter((c) => c !== void 0);
|
|
5703
|
+
for (const c of prev.collections) if (!reordered.some((r) => r.name === c.name)) reordered.push(c);
|
|
5704
|
+
return {
|
|
5705
|
+
...prev,
|
|
5706
|
+
collections: reordered
|
|
5707
|
+
};
|
|
5708
|
+
});
|
|
5709
|
+
shared.succeed("已临时调整顺序(本地生效,重启宿主后持久化)");
|
|
5710
|
+
} else shared.fail(msg);
|
|
5711
|
+
} finally {
|
|
5712
|
+
shared.setTagBusy(false);
|
|
5713
|
+
}
|
|
5714
|
+
}, [
|
|
5715
|
+
api,
|
|
5716
|
+
loadGroups,
|
|
5717
|
+
shared
|
|
5718
|
+
]),
|
|
5719
|
+
reorderSourceGroups: (0, react.useCallback)(async (orderedKeys) => {
|
|
5720
|
+
shared.setTagBusy(true);
|
|
5721
|
+
shared.clearFail();
|
|
5409
5722
|
try {
|
|
5410
|
-
|
|
5723
|
+
await api.reorderSourceGroups(orderedKeys);
|
|
5724
|
+
await loadGroups();
|
|
5725
|
+
} catch (error) {
|
|
5726
|
+
const msg = errorMessage(error);
|
|
5727
|
+
if (msg.includes("404") || msg.toLowerCase().includes("not found")) {
|
|
5728
|
+
setGroupsState((prev) => {
|
|
5729
|
+
if (prev === null) return prev;
|
|
5730
|
+
const colOrder = orderedKeys.filter((k) => k.startsWith("col:")).map((k) => k.slice(4));
|
|
5731
|
+
const map = new Map(prev.collections.map((c) => [c.name, c]));
|
|
5732
|
+
const reordered = colOrder.map((n) => map.get(n)).filter((c) => c !== void 0);
|
|
5733
|
+
for (const c of prev.collections) if (!reordered.some((r) => r.name === c.name)) reordered.push(c);
|
|
5734
|
+
return {
|
|
5735
|
+
...prev,
|
|
5736
|
+
collections: reordered,
|
|
5737
|
+
sourceGroupOrder: orderedKeys
|
|
5738
|
+
};
|
|
5739
|
+
});
|
|
5740
|
+
shared.succeed("已临时调整顺序(本地生效,重启宿主后持久化)");
|
|
5741
|
+
} else shared.fail(msg);
|
|
5742
|
+
} finally {
|
|
5743
|
+
shared.setTagBusy(false);
|
|
5744
|
+
}
|
|
5745
|
+
}, [
|
|
5746
|
+
api,
|
|
5747
|
+
loadGroups,
|
|
5748
|
+
shared
|
|
5749
|
+
])
|
|
5750
|
+
};
|
|
5751
|
+
}
|
|
5752
|
+
//#endregion
|
|
5753
|
+
//#region src/client/panel/hooks/useSourceFlow.ts
|
|
5754
|
+
/**
|
|
5755
|
+
* useSourceFlow — 来源域:来源列表/更新检查/同步确认/跟进删除/回收站恢复/
|
|
5756
|
+
* 清空回收站,以及单个技能/整组的删除确认流。目录与分组的刷新经聚合根
|
|
5757
|
+
* 传入的 reload 回调,不直接碰其他域的 state。
|
|
5758
|
+
*/
|
|
5759
|
+
function useSourceFlow(api, shared, reloadCatalog, reloadGroups) {
|
|
5760
|
+
const [sourcesState, setSourcesState] = (0, react.useState)(null);
|
|
5761
|
+
const [sourceCheck, setSourceCheck] = (0, react.useState)({});
|
|
5762
|
+
const [checkingSource, setCheckingSource] = (0, react.useState)(null);
|
|
5763
|
+
const [syncingSource, setSyncingSource] = (0, react.useState)(null);
|
|
5764
|
+
const [confirmDialog, setConfirmDialog] = (0, react.useState)(null);
|
|
5765
|
+
const [deleteSkillDialog, setDeleteSkillDialog] = (0, react.useState)(null);
|
|
5766
|
+
const [deleteGroupDialog, setDeleteGroupDialog] = (0, react.useState)(null);
|
|
5767
|
+
const [confirmClearTrash, setConfirmClearTrash] = (0, react.useState)(false);
|
|
5768
|
+
/** 加载来源记录 + 回收站。 */
|
|
5769
|
+
const loadSources = (0, react.useCallback)(async () => {
|
|
5770
|
+
try {
|
|
5771
|
+
setSourcesState(await api.sources());
|
|
5772
|
+
} catch (error) {
|
|
5773
|
+
shared.fail(errorMessage(error));
|
|
5774
|
+
}
|
|
5775
|
+
}, [api, shared]);
|
|
5776
|
+
return {
|
|
5777
|
+
sourcesState,
|
|
5778
|
+
sourceCheck,
|
|
5779
|
+
checkingSource,
|
|
5780
|
+
syncingSource,
|
|
5781
|
+
confirmDialog,
|
|
5782
|
+
deleteSkillDialog,
|
|
5783
|
+
deleteGroupDialog,
|
|
5784
|
+
confirmClearTrash,
|
|
5785
|
+
setConfirmDialog,
|
|
5786
|
+
setDeleteSkillDialog,
|
|
5787
|
+
setDeleteGroupDialog,
|
|
5788
|
+
setConfirmClearTrash,
|
|
5789
|
+
loadSources,
|
|
5790
|
+
checkSources: (0, react.useCallback)(async (repo) => {
|
|
5791
|
+
setCheckingSource(repo ?? "all");
|
|
5792
|
+
shared.clearFail();
|
|
5793
|
+
try {
|
|
5794
|
+
const result = await api.checkSources(repo);
|
|
5795
|
+
const next = { ...sourceCheck };
|
|
5796
|
+
for (const item of result.results) next[item.repo] = item;
|
|
5797
|
+
setSourceCheck(next);
|
|
5798
|
+
} catch (error) {
|
|
5799
|
+
shared.fail(errorMessage(error));
|
|
5800
|
+
} finally {
|
|
5801
|
+
setCheckingSource(null);
|
|
5802
|
+
}
|
|
5803
|
+
}, [
|
|
5804
|
+
api,
|
|
5805
|
+
sourceCheck,
|
|
5806
|
+
shared
|
|
5807
|
+
]),
|
|
5808
|
+
requestSync: (0, react.useCallback)((repo, skills) => {
|
|
5809
|
+
setConfirmDialog({
|
|
5810
|
+
kind: "sync",
|
|
5811
|
+
repo,
|
|
5812
|
+
skills
|
|
5813
|
+
});
|
|
5814
|
+
}, []),
|
|
5815
|
+
requestDelete: (0, react.useCallback)((repo, skills) => {
|
|
5816
|
+
setConfirmDialog({
|
|
5817
|
+
kind: "delete",
|
|
5818
|
+
repo,
|
|
5819
|
+
skills
|
|
5820
|
+
});
|
|
5821
|
+
}, []),
|
|
5822
|
+
runConfirmed: (0, react.useCallback)(async () => {
|
|
5823
|
+
const dialog = confirmDialog;
|
|
5824
|
+
if (dialog === null) return;
|
|
5825
|
+
setConfirmDialog(null);
|
|
5826
|
+
if (dialog.kind === "sync") {
|
|
5827
|
+
setSyncingSource(dialog.repo);
|
|
5828
|
+
shared.clearFail();
|
|
5829
|
+
try {
|
|
5830
|
+
const result = await api.syncSource(dialog.repo, dialog.skills);
|
|
5831
|
+
await Promise.all([
|
|
5832
|
+
reloadCatalog(),
|
|
5833
|
+
reloadGroups(),
|
|
5834
|
+
loadSources()
|
|
5835
|
+
]);
|
|
5836
|
+
if (result.failed.length > 0) shared.fail("sync: " + result.failed.map((failure) => failure.name + ": " + failure.error).join("; "));
|
|
5837
|
+
} catch (error) {
|
|
5838
|
+
shared.fail(errorMessage(error));
|
|
5839
|
+
} finally {
|
|
5840
|
+
setSyncingSource(null);
|
|
5841
|
+
}
|
|
5842
|
+
} else {
|
|
5843
|
+
shared.clearFail();
|
|
5844
|
+
try {
|
|
5845
|
+
await api.confirmDeleteSource(dialog.repo, dialog.skills);
|
|
5846
|
+
await Promise.all([
|
|
5847
|
+
reloadCatalog(),
|
|
5848
|
+
reloadGroups(),
|
|
5849
|
+
loadSources()
|
|
5850
|
+
]);
|
|
5851
|
+
} catch (error) {
|
|
5852
|
+
shared.fail(errorMessage(error));
|
|
5853
|
+
}
|
|
5854
|
+
}
|
|
5855
|
+
}, [
|
|
5856
|
+
confirmDialog,
|
|
5857
|
+
api,
|
|
5858
|
+
reloadCatalog,
|
|
5859
|
+
reloadGroups,
|
|
5860
|
+
loadSources,
|
|
5861
|
+
shared
|
|
5862
|
+
]),
|
|
5863
|
+
restoreTrash: (0, react.useCallback)(async (name) => {
|
|
5864
|
+
shared.setTagBusy(true);
|
|
5865
|
+
shared.clearFail();
|
|
5866
|
+
try {
|
|
5867
|
+
await api.restoreSource(name);
|
|
5411
5868
|
await Promise.all([
|
|
5412
|
-
|
|
5413
|
-
|
|
5869
|
+
reloadCatalog(),
|
|
5870
|
+
reloadGroups(),
|
|
5414
5871
|
loadSources()
|
|
5415
5872
|
]);
|
|
5416
|
-
if (result.failed.length > 0) setLoadError("sync: " + result.failed.map((failure) => failure.name + ": " + failure.error).join("; "));
|
|
5417
5873
|
} catch (error) {
|
|
5418
|
-
|
|
5874
|
+
shared.fail(errorMessage(error));
|
|
5419
5875
|
} finally {
|
|
5420
|
-
|
|
5876
|
+
shared.setTagBusy(false);
|
|
5421
5877
|
}
|
|
5422
|
-
}
|
|
5423
|
-
|
|
5878
|
+
}, [
|
|
5879
|
+
api,
|
|
5880
|
+
reloadCatalog,
|
|
5881
|
+
reloadGroups,
|
|
5882
|
+
loadSources,
|
|
5883
|
+
shared
|
|
5884
|
+
]),
|
|
5885
|
+
clearTrash: (0, react.useCallback)(async () => {
|
|
5886
|
+
setConfirmClearTrash(false);
|
|
5887
|
+
shared.setTagBusy(true);
|
|
5888
|
+
shared.clearFail();
|
|
5424
5889
|
try {
|
|
5425
|
-
await api.
|
|
5890
|
+
const result = await api.clearTrash();
|
|
5891
|
+
if (result.failed.length > 0) shared.fail("clear trash: " + result.failed.map((failure) => failure.name + ": " + failure.error).join("; "));
|
|
5892
|
+
await Promise.all([reloadCatalog(), loadSources()]);
|
|
5893
|
+
} catch (error) {
|
|
5894
|
+
shared.fail(errorMessage(error));
|
|
5895
|
+
} finally {
|
|
5896
|
+
shared.setTagBusy(false);
|
|
5897
|
+
}
|
|
5898
|
+
}, [
|
|
5899
|
+
api,
|
|
5900
|
+
reloadCatalog,
|
|
5901
|
+
loadSources,
|
|
5902
|
+
shared
|
|
5903
|
+
]),
|
|
5904
|
+
requestDeleteSkill: (0, react.useCallback)((name) => {
|
|
5905
|
+
setDeleteSkillDialog(name);
|
|
5906
|
+
}, []),
|
|
5907
|
+
runDeleteSkill: (0, react.useCallback)(async () => {
|
|
5908
|
+
const name = deleteSkillDialog;
|
|
5909
|
+
if (name === null) return;
|
|
5910
|
+
setDeleteSkillDialog(null);
|
|
5911
|
+
shared.setTagBusy(true);
|
|
5912
|
+
shared.clearFail();
|
|
5913
|
+
try {
|
|
5914
|
+
await api.deleteSkill(name);
|
|
5426
5915
|
await Promise.all([
|
|
5427
|
-
|
|
5428
|
-
|
|
5916
|
+
reloadCatalog(),
|
|
5917
|
+
reloadGroups(),
|
|
5429
5918
|
loadSources()
|
|
5430
5919
|
]);
|
|
5431
5920
|
} catch (error) {
|
|
5432
|
-
|
|
5921
|
+
shared.fail(errorMessage(error));
|
|
5922
|
+
} finally {
|
|
5923
|
+
shared.setTagBusy(false);
|
|
5433
5924
|
}
|
|
5434
|
-
}
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
try {
|
|
5447
|
-
const result = await api.checkSources(repo);
|
|
5448
|
-
const next = { ...sourceCheck };
|
|
5449
|
-
for (const item of result.results) next[item.repo] = item;
|
|
5450
|
-
setSourceCheck(next);
|
|
5451
|
-
} catch (error) {
|
|
5452
|
-
setLoadError(errorMessage(error));
|
|
5453
|
-
} finally {
|
|
5454
|
-
setCheckingSource(null);
|
|
5455
|
-
}
|
|
5456
|
-
}, [api, sourceCheck]);
|
|
5457
|
-
/** 请求同步某个来源的所选技能(弹确认,因为会覆盖本地修改)。 */
|
|
5458
|
-
const requestSync = (0, react.useCallback)((repo, skills) => {
|
|
5459
|
-
setConfirmDialog({
|
|
5460
|
-
kind: "sync",
|
|
5461
|
-
repo,
|
|
5462
|
-
skills
|
|
5463
|
-
});
|
|
5464
|
-
}, []);
|
|
5465
|
-
/** 请求跟进上游删除(弹确认,移入回收站)。 */
|
|
5466
|
-
const requestDelete = (0, react.useCallback)((repo, skills) => {
|
|
5467
|
-
setConfirmDialog({
|
|
5468
|
-
kind: "delete",
|
|
5469
|
-
repo,
|
|
5470
|
-
skills
|
|
5471
|
-
});
|
|
5472
|
-
}, []);
|
|
5473
|
-
/** 从回收站恢复一个技能。 */
|
|
5474
|
-
const restoreTrash = (0, react.useCallback)(async (name) => {
|
|
5475
|
-
setTagBusy(true);
|
|
5476
|
-
setLoadError(null);
|
|
5477
|
-
try {
|
|
5478
|
-
await api.restoreSource(name);
|
|
5479
|
-
await Promise.all([
|
|
5480
|
-
load(),
|
|
5481
|
-
loadGroups(),
|
|
5482
|
-
loadSources()
|
|
5483
|
-
]);
|
|
5484
|
-
} catch (error) {
|
|
5485
|
-
setLoadError(errorMessage(error));
|
|
5486
|
-
} finally {
|
|
5487
|
-
setTagBusy(false);
|
|
5488
|
-
}
|
|
5489
|
-
}, [
|
|
5490
|
-
api,
|
|
5491
|
-
load,
|
|
5492
|
-
loadGroups,
|
|
5493
|
-
loadSources
|
|
5494
|
-
]);
|
|
5495
|
-
/** 确认后永久删除回收站里的全部技能。 */
|
|
5496
|
-
const clearTrash = (0, react.useCallback)(async () => {
|
|
5497
|
-
setConfirmClearTrash(false);
|
|
5498
|
-
setTagBusy(true);
|
|
5499
|
-
setLoadError(null);
|
|
5500
|
-
try {
|
|
5501
|
-
const result = await api.clearTrash();
|
|
5502
|
-
if (result.failed.length > 0) setLoadError("clear trash: " + result.failed.map((failure) => failure.name + ": " + failure.error).join("; "));
|
|
5503
|
-
await Promise.all([load(), loadSources()]);
|
|
5504
|
-
} catch (error) {
|
|
5505
|
-
setLoadError(errorMessage(error));
|
|
5506
|
-
} finally {
|
|
5507
|
-
setTagBusy(false);
|
|
5508
|
-
}
|
|
5509
|
-
}, [
|
|
5510
|
-
api,
|
|
5511
|
-
load,
|
|
5512
|
-
loadSources
|
|
5513
|
-
]);
|
|
5514
|
-
const [fixingPaths, setFixingPaths] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
5515
|
-
const fixDiagnostic = (0, react.useCallback)(async (path) => {
|
|
5516
|
-
setFixingPaths((previous) => new Set(previous).add(path));
|
|
5517
|
-
setLoadError(null);
|
|
5518
|
-
try {
|
|
5519
|
-
if (!window.confirm(tt("diag.fixConfirm", { path }))) return;
|
|
5520
|
-
await api.fixDiagnostic(path);
|
|
5521
|
-
await load();
|
|
5522
|
-
setSuccessBanner(tt("diag.fixed"));
|
|
5523
|
-
} catch (error) {
|
|
5524
|
-
setLoadError(errorMessage(error));
|
|
5525
|
-
} finally {
|
|
5526
|
-
setFixingPaths((previous) => {
|
|
5527
|
-
const next = new Set(previous);
|
|
5528
|
-
next.delete(path);
|
|
5529
|
-
return next;
|
|
5925
|
+
}, [
|
|
5926
|
+
api,
|
|
5927
|
+
deleteSkillDialog,
|
|
5928
|
+
reloadCatalog,
|
|
5929
|
+
reloadGroups,
|
|
5930
|
+
loadSources,
|
|
5931
|
+
shared
|
|
5932
|
+
]),
|
|
5933
|
+
requestDeleteGroup: (0, react.useCallback)((name, skillNames) => {
|
|
5934
|
+
setDeleteGroupDialog({
|
|
5935
|
+
name,
|
|
5936
|
+
skillNames
|
|
5530
5937
|
});
|
|
5531
|
-
}
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5938
|
+
}, []),
|
|
5939
|
+
runDeleteGroup: (0, react.useCallback)(async () => {
|
|
5940
|
+
const dialog = deleteGroupDialog;
|
|
5941
|
+
if (dialog === null) return;
|
|
5942
|
+
setDeleteGroupDialog(null);
|
|
5943
|
+
shared.setTagBusy(true);
|
|
5944
|
+
shared.clearFail();
|
|
5945
|
+
const failures = [];
|
|
5946
|
+
let done = 0;
|
|
5947
|
+
for (const name of dialog.skillNames) try {
|
|
5948
|
+
await api.deleteSkill(name);
|
|
5949
|
+
done += 1;
|
|
5950
|
+
} catch (error) {
|
|
5951
|
+
failures.push(name + ": " + errorMessage(error));
|
|
5952
|
+
}
|
|
5546
5953
|
await Promise.all([
|
|
5547
|
-
|
|
5548
|
-
|
|
5954
|
+
reloadCatalog(),
|
|
5955
|
+
reloadGroups(),
|
|
5549
5956
|
loadSources()
|
|
5550
5957
|
]);
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
]);
|
|
5600
|
-
/** 新建一个空 tag 分组。 */
|
|
5601
|
-
const createTag = (0, react.useCallback)(async (event) => {
|
|
5602
|
-
event.preventDefault();
|
|
5603
|
-
const name = newTagName.trim();
|
|
5604
|
-
if (name === "") return;
|
|
5605
|
-
setTagBusy(true);
|
|
5606
|
-
setLoadError(null);
|
|
5607
|
-
try {
|
|
5608
|
-
applyTags(await api.saveTag({ name }));
|
|
5609
|
-
setNewTagName("");
|
|
5610
|
-
} catch (error) {
|
|
5611
|
-
setLoadError(errorMessage(error));
|
|
5612
|
-
} finally {
|
|
5613
|
-
setTagBusy(false);
|
|
5614
|
-
}
|
|
5615
|
-
}, [
|
|
5616
|
-
api,
|
|
5617
|
-
applyTags,
|
|
5618
|
-
newTagName
|
|
5619
|
-
]);
|
|
5620
|
-
/** 删除一个 tag 分组(不影响技能文件)。 */
|
|
5621
|
-
const deleteTag = (0, react.useCallback)(async (id) => {
|
|
5622
|
-
setTagBusy(true);
|
|
5623
|
-
setLoadError(null);
|
|
5624
|
-
try {
|
|
5625
|
-
applyTags(await api.deleteTag(id));
|
|
5626
|
-
setEditingTag(null);
|
|
5627
|
-
} catch (error) {
|
|
5628
|
-
setLoadError(errorMessage(error));
|
|
5629
|
-
} finally {
|
|
5630
|
-
setTagBusy(false);
|
|
5631
|
-
}
|
|
5632
|
-
}, [api, applyTags]);
|
|
5633
|
-
/** 重命名 tag,或保存成员勾选后回到列表。 */
|
|
5634
|
-
const saveTag = (0, react.useCallback)(async (id, name, memberNames) => {
|
|
5635
|
-
setTagBusy(true);
|
|
5636
|
-
setLoadError(null);
|
|
5958
|
+
shared.setTagBusy(false);
|
|
5959
|
+
if (failures.length > 0) shared.fail(`删除整组 "${dialog.name}":成功 ${done} 个,失败 ${failures.length} 个:` + failures.join("; "));
|
|
5960
|
+
else if (done > 0) shared.succeed(`已删除整组 "${dialog.name}":${done} 个技能已移入回收站`);
|
|
5961
|
+
}, [
|
|
5962
|
+
api,
|
|
5963
|
+
deleteGroupDialog,
|
|
5964
|
+
reloadCatalog,
|
|
5965
|
+
reloadGroups,
|
|
5966
|
+
loadSources,
|
|
5967
|
+
shared
|
|
5968
|
+
])
|
|
5969
|
+
};
|
|
5970
|
+
}
|
|
5971
|
+
//#endregion
|
|
5972
|
+
//#region src/client/panel/hooks/useMarketFlow.ts
|
|
5973
|
+
/**
|
|
5974
|
+
* useMarketFlow — 市场域:市场源增删、仓库扫描、分支/版本选择、导入任务
|
|
5975
|
+
* (轮询+取消)、更新检查、星标统计、版本同步、全部更新。目录/分组/来源
|
|
5976
|
+
* 的刷新与来源检查经聚合根传入,不直接碰其他域的 state。
|
|
5977
|
+
*/
|
|
5978
|
+
function useMarketFlow(api, shared, reloadCatalog, reloadGroups, reloadSources, checkSources, sourceCheck) {
|
|
5979
|
+
const [marketState, setMarketState] = (0, react.useState)({
|
|
5980
|
+
status: "loading",
|
|
5981
|
+
repos: []
|
|
5982
|
+
});
|
|
5983
|
+
const [marketCheck, setMarketCheck] = (0, react.useState)({});
|
|
5984
|
+
const [branchChoice, setBranchChoice] = (0, react.useState)(null);
|
|
5985
|
+
const [branchBusy, setBranchBusy] = (0, react.useState)(false);
|
|
5986
|
+
const [marketSyncDialog, setMarketSyncDialog] = (0, react.useState)(null);
|
|
5987
|
+
const [syncingMarket, setSyncingMarket] = (0, react.useState)(null);
|
|
5988
|
+
const [syncBusy, setSyncBusy] = (0, react.useState)(false);
|
|
5989
|
+
const [newSourceName, setNewSourceName] = (0, react.useState)("");
|
|
5990
|
+
const [repoDiscoverState, setRepoDiscoverState] = (0, react.useState)({ status: "idle" });
|
|
5991
|
+
/** Market repo currently being scanned (null when idle); per-row busy state. */
|
|
5992
|
+
const [scanningRepo, setScanningRepo] = (0, react.useState)(null);
|
|
5993
|
+
const [repoSelected, setRepoSelected] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
5994
|
+
const [repoImporting, setRepoImporting] = (0, react.useState)(false);
|
|
5995
|
+
const [repoResult, setRepoResult] = (0, react.useState)(null);
|
|
5996
|
+
const [importJobId, setImportJobId] = (0, react.useState)(null);
|
|
5997
|
+
const pollAbortRef = (0, react.useRef)(null);
|
|
5998
|
+
/** 市场源星星/下载数(SWR 缓存)。 */
|
|
5999
|
+
const [marketStats, setMarketStats] = (0, react.useState)({});
|
|
6000
|
+
/** 版本对话框:打开时拉 releases + branches,确认后定版并重扫。 */
|
|
6001
|
+
const [versionDialog, setVersionDialog] = (0, react.useState)(null);
|
|
6002
|
+
const [versionBusy, setVersionBusy] = (0, react.useState)(false);
|
|
6003
|
+
const [updateAllDialog, setUpdateAllDialog] = (0, react.useState)(false);
|
|
6004
|
+
/** 加载市场源列表。 */
|
|
6005
|
+
const loadMarket = (0, react.useCallback)(async () => {
|
|
5637
6006
|
try {
|
|
5638
|
-
const
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
name: safeName
|
|
6007
|
+
const next = await api.market();
|
|
6008
|
+
setMarketState({
|
|
6009
|
+
status: "ready",
|
|
6010
|
+
repos: next.repos
|
|
5643
6011
|
});
|
|
5644
|
-
if (memberNames !== null) tags = await api.setTagMembers(id, memberNames);
|
|
5645
|
-
if (tags === null) return;
|
|
5646
|
-
applyTags(tags);
|
|
5647
|
-
setEditingTag(null);
|
|
5648
6012
|
} catch (error) {
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
}
|
|
5653
|
-
}, [api, applyTags]);
|
|
5654
|
-
/** 拖拽重排场景分组 */
|
|
5655
|
-
const reorderTags = (0, react.useCallback)(async (orderedIds) => {
|
|
5656
|
-
setTagBusy(true);
|
|
5657
|
-
setLoadError(null);
|
|
5658
|
-
try {
|
|
5659
|
-
const tags = await api.reorderTags(orderedIds);
|
|
5660
|
-
applyTags(tags);
|
|
5661
|
-
} catch (error) {
|
|
5662
|
-
const msg = errorMessage(error);
|
|
5663
|
-
if (msg.includes("404") || msg.toLowerCase().includes("not found")) {
|
|
5664
|
-
const byId = new Map(groupsState?.tags.map((t) => [t.id, t]) ?? []);
|
|
5665
|
-
const reordered = orderedIds.map((id) => byId.get(id)).filter((t) => t !== void 0);
|
|
5666
|
-
if (reordered.length === orderedIds.length) {
|
|
5667
|
-
applyTags(reordered);
|
|
5668
|
-
setSuccessBanner("已临时调整顺序(本地生效,重启宿主后持久化)");
|
|
5669
|
-
} else setLoadError(msg);
|
|
5670
|
-
} else setLoadError(msg);
|
|
5671
|
-
} finally {
|
|
5672
|
-
setTagBusy(false);
|
|
5673
|
-
}
|
|
5674
|
-
}, [
|
|
5675
|
-
api,
|
|
5676
|
-
applyTags,
|
|
5677
|
-
groupsState
|
|
5678
|
-
]);
|
|
5679
|
-
/** 拖拽重排来源集合 */
|
|
5680
|
-
const reorderCollections = (0, react.useCallback)(async (orderedNames) => {
|
|
5681
|
-
setTagBusy(true);
|
|
5682
|
-
setLoadError(null);
|
|
5683
|
-
try {
|
|
5684
|
-
const collections = await api.reorderCollections(orderedNames);
|
|
5685
|
-
setGroupsState((prev) => prev === null ? prev : {
|
|
5686
|
-
...prev,
|
|
5687
|
-
collections
|
|
6013
|
+
setMarketState({
|
|
6014
|
+
status: "error",
|
|
6015
|
+
repos: []
|
|
5688
6016
|
});
|
|
5689
|
-
|
|
5690
|
-
} catch (error) {
|
|
5691
|
-
const msg = errorMessage(error);
|
|
5692
|
-
if (msg.includes("404") || msg.toLowerCase().includes("not found")) {
|
|
5693
|
-
setGroupsState((prev) => {
|
|
5694
|
-
if (prev === null) return prev;
|
|
5695
|
-
const map = new Map(prev.collections.map((c) => [c.name, c]));
|
|
5696
|
-
const reordered = orderedNames.map((n) => map.get(n)).filter((c) => c !== void 0);
|
|
5697
|
-
for (const c of prev.collections) if (!reordered.some((r) => r.name === c.name)) reordered.push(c);
|
|
5698
|
-
return {
|
|
5699
|
-
...prev,
|
|
5700
|
-
collections: reordered
|
|
5701
|
-
};
|
|
5702
|
-
});
|
|
5703
|
-
setSuccessBanner("已临时调整顺序(本地生效,重启宿主后持久化)");
|
|
5704
|
-
} else setLoadError(msg);
|
|
5705
|
-
} finally {
|
|
5706
|
-
setTagBusy(false);
|
|
5707
|
-
}
|
|
5708
|
-
}, [api, loadGroups]);
|
|
5709
|
-
/** 拖拽重排来源顶层分组(project / col:xxx / personal 全量可拖) */
|
|
5710
|
-
const reorderSourceGroups = (0, react.useCallback)(async (orderedKeys) => {
|
|
5711
|
-
setTagBusy(true);
|
|
5712
|
-
setLoadError(null);
|
|
5713
|
-
try {
|
|
5714
|
-
await api.reorderSourceGroups(orderedKeys);
|
|
5715
|
-
await loadGroups();
|
|
5716
|
-
} catch (error) {
|
|
5717
|
-
const msg = errorMessage(error);
|
|
5718
|
-
if (msg.includes("404") || msg.toLowerCase().includes("not found")) {
|
|
5719
|
-
setGroupsState((prev) => {
|
|
5720
|
-
if (prev === null) return prev;
|
|
5721
|
-
const colOrder = orderedKeys.filter((k) => k.startsWith("col:")).map((k) => k.slice(4));
|
|
5722
|
-
const map = new Map(prev.collections.map((c) => [c.name, c]));
|
|
5723
|
-
const reordered = colOrder.map((n) => map.get(n)).filter((c) => c !== void 0);
|
|
5724
|
-
for (const c of prev.collections) if (!reordered.some((r) => r.name === c.name)) reordered.push(c);
|
|
5725
|
-
return {
|
|
5726
|
-
...prev,
|
|
5727
|
-
collections: reordered,
|
|
5728
|
-
sourceGroupOrder: orderedKeys
|
|
5729
|
-
};
|
|
5730
|
-
});
|
|
5731
|
-
setSuccessBanner("已临时调整顺序(本地生效,重启宿主后持久化)");
|
|
5732
|
-
} else setLoadError(msg);
|
|
5733
|
-
} finally {
|
|
5734
|
-
setTagBusy(false);
|
|
6017
|
+
shared.fail(errorMessage(error));
|
|
5735
6018
|
}
|
|
5736
|
-
}, [api,
|
|
6019
|
+
}, [api, shared]);
|
|
5737
6020
|
/** 添加一个市场源(内置市场目录与手动输入共用),并立即扫描它。 */
|
|
5738
6021
|
const addSource = (0, react.useCallback)(async (input) => {
|
|
5739
6022
|
const value = input.trim();
|
|
5740
6023
|
if (value === "") return;
|
|
5741
|
-
|
|
6024
|
+
shared.clearFail();
|
|
5742
6025
|
try {
|
|
5743
6026
|
const result = await api.addMarketSource(value);
|
|
5744
6027
|
setMarketState({
|
|
@@ -5766,17 +6049,17 @@ window.__ModuleLoader__.load({
|
|
|
5766
6049
|
setScanningRepo(null);
|
|
5767
6050
|
}
|
|
5768
6051
|
} catch (error) {
|
|
5769
|
-
|
|
6052
|
+
shared.fail(errorMessage(error));
|
|
5770
6053
|
}
|
|
5771
|
-
}, [api]);
|
|
6054
|
+
}, [api, shared]);
|
|
5772
6055
|
/** 手动输入框的添加动作。 */
|
|
5773
6056
|
const addMarketSource = (0, react.useCallback)(async () => {
|
|
5774
6057
|
await addSource(newSourceName);
|
|
5775
6058
|
}, [addSource, newSourceName]);
|
|
5776
6059
|
/** 删除一个市场源(不影响已装技能)。 */
|
|
5777
6060
|
const removeMarketSource = (0, react.useCallback)(async (repo) => {
|
|
5778
|
-
setTagBusy(true);
|
|
5779
|
-
|
|
6061
|
+
shared.setTagBusy(true);
|
|
6062
|
+
shared.clearFail();
|
|
5780
6063
|
try {
|
|
5781
6064
|
const result = await api.removeMarketSource(repo);
|
|
5782
6065
|
setMarketState({
|
|
@@ -5784,11 +6067,11 @@ window.__ModuleLoader__.load({
|
|
|
5784
6067
|
repos: result.repos
|
|
5785
6068
|
});
|
|
5786
6069
|
} catch (error) {
|
|
5787
|
-
|
|
6070
|
+
shared.fail(errorMessage(error));
|
|
5788
6071
|
} finally {
|
|
5789
|
-
setTagBusy(false);
|
|
6072
|
+
shared.setTagBusy(false);
|
|
5790
6073
|
}
|
|
5791
|
-
}, [api]);
|
|
6074
|
+
}, [api, shared]);
|
|
5792
6075
|
/** 扫描一个市场源(或手动输入)的仓库。 */
|
|
5793
6076
|
const scanRepo = (0, react.useCallback)(async (input) => {
|
|
5794
6077
|
const value = input.trim();
|
|
@@ -5797,7 +6080,7 @@ window.__ModuleLoader__.load({
|
|
|
5797
6080
|
setRepoSelected(/* @__PURE__ */ new Set());
|
|
5798
6081
|
setScanningRepo(value);
|
|
5799
6082
|
setRepoDiscoverState({ status: "scanning" });
|
|
5800
|
-
|
|
6083
|
+
shared.clearFail();
|
|
5801
6084
|
try {
|
|
5802
6085
|
const data = await api.repoDiscover(value);
|
|
5803
6086
|
if (data.ref === null) {
|
|
@@ -5822,29 +6105,31 @@ window.__ModuleLoader__.load({
|
|
|
5822
6105
|
} finally {
|
|
5823
6106
|
setScanningRepo(null);
|
|
5824
6107
|
}
|
|
5825
|
-
}, [
|
|
6108
|
+
}, [
|
|
6109
|
+
api,
|
|
6110
|
+
loadMarket,
|
|
6111
|
+
shared
|
|
6112
|
+
]);
|
|
5826
6113
|
/** 确认分支选择:持久化 ref 后重新扫描。 */
|
|
5827
6114
|
const confirmBranchChoice = (0, react.useCallback)(async () => {
|
|
5828
6115
|
if (branchChoice === null) return;
|
|
5829
6116
|
setBranchBusy(true);
|
|
5830
|
-
|
|
6117
|
+
shared.clearFail();
|
|
5831
6118
|
try {
|
|
5832
6119
|
await api.setMarketSourceRef(branchChoice.repo, branchChoice.selected);
|
|
5833
6120
|
setBranchChoice(null);
|
|
5834
6121
|
await scanRepo(branchChoice.repo);
|
|
5835
6122
|
} catch (error) {
|
|
5836
|
-
|
|
6123
|
+
shared.fail(errorMessage(error));
|
|
5837
6124
|
} finally {
|
|
5838
6125
|
setBranchBusy(false);
|
|
5839
6126
|
}
|
|
5840
6127
|
}, [
|
|
5841
6128
|
api,
|
|
5842
6129
|
branchChoice,
|
|
5843
|
-
scanRepo
|
|
6130
|
+
scanRepo,
|
|
6131
|
+
shared
|
|
5844
6132
|
]);
|
|
5845
|
-
/** 版本对话框:打开时拉 releases + branches,确认后定版并重扫。 */
|
|
5846
|
-
const [versionDialog, setVersionDialog] = (0, react.useState)(null);
|
|
5847
|
-
const [versionBusy, setVersionBusy] = (0, react.useState)(false);
|
|
5848
6133
|
const openVersionDialog = (0, react.useCallback)(async (repo) => {
|
|
5849
6134
|
const current = marketState.repos.find((item) => item.repo === repo)?.ref;
|
|
5850
6135
|
setVersionDialog({
|
|
@@ -5856,7 +6141,7 @@ window.__ModuleLoader__.load({
|
|
|
5856
6141
|
custom: "",
|
|
5857
6142
|
loading: true
|
|
5858
6143
|
});
|
|
5859
|
-
|
|
6144
|
+
shared.clearFail();
|
|
5860
6145
|
try {
|
|
5861
6146
|
const data = await api.marketSourceVersions(repo);
|
|
5862
6147
|
setVersionDialog({
|
|
@@ -5869,23 +6154,27 @@ window.__ModuleLoader__.load({
|
|
|
5869
6154
|
loading: false
|
|
5870
6155
|
});
|
|
5871
6156
|
} catch (error) {
|
|
5872
|
-
|
|
6157
|
+
shared.fail(errorMessage(error));
|
|
5873
6158
|
setVersionDialog(null);
|
|
5874
6159
|
}
|
|
5875
|
-
}, [
|
|
6160
|
+
}, [
|
|
6161
|
+
api,
|
|
6162
|
+
marketState,
|
|
6163
|
+
shared
|
|
6164
|
+
]);
|
|
5876
6165
|
const confirmVersionDialog = (0, react.useCallback)(async () => {
|
|
5877
6166
|
if (versionDialog === null) return;
|
|
5878
6167
|
const ref = versionDialog.custom.trim() !== "" ? versionDialog.custom.trim() : versionDialog.selected;
|
|
5879
6168
|
if (ref === "") return;
|
|
5880
6169
|
setVersionBusy(true);
|
|
5881
|
-
|
|
6170
|
+
shared.clearFail();
|
|
5882
6171
|
try {
|
|
5883
6172
|
await api.setMarketSourceRef(versionDialog.repo, ref);
|
|
5884
6173
|
setVersionDialog(null);
|
|
5885
6174
|
await loadMarket();
|
|
5886
6175
|
await scanRepo(versionDialog.repo);
|
|
5887
6176
|
} catch (error) {
|
|
5888
|
-
|
|
6177
|
+
shared.fail(errorMessage(error));
|
|
5889
6178
|
} finally {
|
|
5890
6179
|
setVersionBusy(false);
|
|
5891
6180
|
}
|
|
@@ -5893,7 +6182,8 @@ window.__ModuleLoader__.load({
|
|
|
5893
6182
|
api,
|
|
5894
6183
|
versionDialog,
|
|
5895
6184
|
loadMarket,
|
|
5896
|
-
scanRepo
|
|
6185
|
+
scanRepo,
|
|
6186
|
+
shared
|
|
5897
6187
|
]);
|
|
5898
6188
|
/** Toggle one repo preview row. */
|
|
5899
6189
|
const toggleRepoSelected = (0, react.useCallback)((path, checked) => {
|
|
@@ -5910,7 +6200,7 @@ window.__ModuleLoader__.load({
|
|
|
5910
6200
|
setRepoImporting(true);
|
|
5911
6201
|
setRepoResult(null);
|
|
5912
6202
|
setImportJobId(null);
|
|
5913
|
-
|
|
6203
|
+
shared.clearFail();
|
|
5914
6204
|
let finalProgress = null;
|
|
5915
6205
|
try {
|
|
5916
6206
|
const created = await api.repoImport(repoDiscoverState.data.repo, [...repoSelected], repoDiscoverState.data.ref ?? void 0);
|
|
@@ -5949,13 +6239,13 @@ window.__ModuleLoader__.load({
|
|
|
5949
6239
|
}
|
|
5950
6240
|
}
|
|
5951
6241
|
await Promise.all([
|
|
5952
|
-
|
|
6242
|
+
reloadCatalog(),
|
|
5953
6243
|
loadMarket(),
|
|
5954
|
-
|
|
5955
|
-
|
|
6244
|
+
reloadGroups(),
|
|
6245
|
+
reloadSources()
|
|
5956
6246
|
]);
|
|
5957
6247
|
} catch (error) {
|
|
5958
|
-
|
|
6248
|
+
shared.fail(errorMessage(error));
|
|
5959
6249
|
} finally {
|
|
5960
6250
|
if (finalProgress !== null && (finalProgress.imported.length > 0 || finalProgress.skipped.length > 0)) {
|
|
5961
6251
|
const doneNames = /* @__PURE__ */ new Set([...finalProgress.imported.map((r) => r.name), ...finalProgress.skipped.map((r) => r.name)]);
|
|
@@ -5985,10 +6275,11 @@ window.__ModuleLoader__.load({
|
|
|
5985
6275
|
api,
|
|
5986
6276
|
repoDiscoverState,
|
|
5987
6277
|
repoSelected,
|
|
5988
|
-
|
|
6278
|
+
reloadCatalog,
|
|
5989
6279
|
loadMarket,
|
|
5990
|
-
|
|
5991
|
-
|
|
6280
|
+
reloadGroups,
|
|
6281
|
+
reloadSources,
|
|
6282
|
+
shared
|
|
5992
6283
|
]);
|
|
5993
6284
|
(0, react.useEffect)(() => {
|
|
5994
6285
|
return () => {
|
|
@@ -6011,11 +6302,15 @@ window.__ModuleLoader__.load({
|
|
|
6011
6302
|
} : prev);
|
|
6012
6303
|
}
|
|
6013
6304
|
} catch (error) {
|
|
6014
|
-
|
|
6305
|
+
shared.fail(errorMessage(error));
|
|
6015
6306
|
} finally {
|
|
6016
6307
|
setRepoImporting(false);
|
|
6017
6308
|
}
|
|
6018
|
-
}, [
|
|
6309
|
+
}, [
|
|
6310
|
+
api,
|
|
6311
|
+
importJobId,
|
|
6312
|
+
shared
|
|
6313
|
+
]);
|
|
6019
6314
|
/** 清空扫描结果(关闭归属卡片) */
|
|
6020
6315
|
const clearScan = (0, react.useCallback)(() => {
|
|
6021
6316
|
setRepoDiscoverState({ status: "idle" });
|
|
@@ -6034,317 +6329,411 @@ window.__ModuleLoader__.load({
|
|
|
6034
6329
|
setMarketCheck(next);
|
|
6035
6330
|
} catch {}
|
|
6036
6331
|
}, [api]);
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6332
|
+
return {
|
|
6333
|
+
marketState,
|
|
6334
|
+
marketCheck,
|
|
6335
|
+
branchChoice,
|
|
6336
|
+
branchBusy,
|
|
6337
|
+
marketSyncDialog,
|
|
6338
|
+
syncingMarket,
|
|
6339
|
+
syncBusy,
|
|
6340
|
+
newSourceName,
|
|
6341
|
+
repoDiscoverState,
|
|
6342
|
+
scanningRepo,
|
|
6343
|
+
repoSelected,
|
|
6344
|
+
repoImporting,
|
|
6345
|
+
repoResult,
|
|
6346
|
+
importJobId,
|
|
6347
|
+
marketStats,
|
|
6348
|
+
versionDialog,
|
|
6349
|
+
versionBusy,
|
|
6350
|
+
updateAllDialog,
|
|
6351
|
+
setBranchChoice,
|
|
6352
|
+
setMarketSyncDialog,
|
|
6353
|
+
setNewSourceName,
|
|
6354
|
+
setRepoSelected,
|
|
6355
|
+
setUpdateAllDialog,
|
|
6356
|
+
setVersionDialog,
|
|
6357
|
+
loadMarket,
|
|
6358
|
+
addSource,
|
|
6359
|
+
addMarketSource,
|
|
6360
|
+
removeMarketSource,
|
|
6361
|
+
scanRepo,
|
|
6362
|
+
confirmBranchChoice,
|
|
6363
|
+
openVersionDialog,
|
|
6364
|
+
confirmVersionDialog,
|
|
6365
|
+
toggleRepoSelected,
|
|
6366
|
+
importRepo,
|
|
6367
|
+
cancelImport,
|
|
6368
|
+
clearScan,
|
|
6369
|
+
checkMarket,
|
|
6370
|
+
loadMarketStats: (0, react.useCallback)(async () => {
|
|
6371
|
+
const merge = (results) => {
|
|
6372
|
+
const partial = {};
|
|
6373
|
+
for (const item of results) if (item.error === void 0) partial[item.repo] = {
|
|
6374
|
+
stars: item.stars,
|
|
6375
|
+
downloads: item.downloads
|
|
6376
|
+
};
|
|
6377
|
+
if (Object.keys(partial).length > 0) setMarketStats((previous) => ({
|
|
6378
|
+
...previous,
|
|
6379
|
+
...partial
|
|
6380
|
+
}));
|
|
6381
|
+
};
|
|
6382
|
+
try {
|
|
6383
|
+
merge((await api.marketStats()).results);
|
|
6384
|
+
} catch {}
|
|
6385
|
+
try {
|
|
6386
|
+
merge((await api.marketStats(true)).results);
|
|
6387
|
+
} catch {}
|
|
6388
|
+
}, [api]),
|
|
6389
|
+
syncMarketSource: (0, react.useCallback)(async (repo) => {
|
|
6390
|
+
setSyncingMarket(repo);
|
|
6391
|
+
shared.clearFail();
|
|
6392
|
+
try {
|
|
6393
|
+
const result = await api.marketSync(repo);
|
|
6394
|
+
setMarketSyncDialog({
|
|
6395
|
+
repo: result.repo,
|
|
6396
|
+
ref: result.ref,
|
|
6397
|
+
skills: result.skills,
|
|
6398
|
+
selected: new Set(result.skills)
|
|
6399
|
+
});
|
|
6400
|
+
await loadMarket();
|
|
6401
|
+
} catch (error) {
|
|
6402
|
+
shared.fail(errorMessage(error));
|
|
6403
|
+
} finally {
|
|
6404
|
+
setSyncingMarket(null);
|
|
6066
6405
|
}
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
marketSyncDialog,
|
|
6082
|
-
load,
|
|
6083
|
-
loadGroups,
|
|
6084
|
-
loadSources,
|
|
6085
|
-
checkMarket
|
|
6086
|
-
]);
|
|
6087
|
-
/**
|
|
6088
|
-
* 一键全部更新:把每个「有可更新技能」的来源逐个同步到上游最新版本
|
|
6089
|
-
* (复用来源同步路径,覆盖本地修改),随后刷新并重新检查。
|
|
6090
|
-
*/
|
|
6091
|
-
const updateAll = (0, react.useCallback)(async () => {
|
|
6092
|
-
setUpdateAllDialog(false);
|
|
6093
|
-
setBatchBusy(true);
|
|
6094
|
-
setLoadError(null);
|
|
6095
|
-
const failures = [];
|
|
6096
|
-
let done = 0;
|
|
6097
|
-
try {
|
|
6098
|
-
for (const [repo, check] of Object.entries(sourceCheck)) {
|
|
6099
|
-
if (!check.changed || check.updated.length === 0) continue;
|
|
6100
|
-
try {
|
|
6101
|
-
const result = await api.syncSource(repo, check.updated);
|
|
6102
|
-
done += 1;
|
|
6103
|
-
if (result.failed.length > 0) failures.push(tt("market.updateAllItem", {
|
|
6104
|
-
repo,
|
|
6105
|
-
count: result.failed.length
|
|
6106
|
-
}) + ": " + result.failed.map((item) => item.name + ": " + item.error).join("; "));
|
|
6107
|
-
} catch (error) {
|
|
6108
|
-
failures.push(repo + ": " + errorMessage(error));
|
|
6406
|
+
}, [
|
|
6407
|
+
api,
|
|
6408
|
+
loadMarket,
|
|
6409
|
+
shared
|
|
6410
|
+
]),
|
|
6411
|
+
confirmMarketSync: (0, react.useCallback)(async () => {
|
|
6412
|
+
if (marketSyncDialog === null) return;
|
|
6413
|
+
const selected = [...marketSyncDialog.selected];
|
|
6414
|
+
setSyncBusy(true);
|
|
6415
|
+
shared.clearFail();
|
|
6416
|
+
try {
|
|
6417
|
+
if (selected.length > 0) {
|
|
6418
|
+
const result = await api.syncSource(marketSyncDialog.repo, selected);
|
|
6419
|
+
if (result.failed.length > 0) shared.fail(result.failed.map((item) => item.name + ": " + item.error).join("\n"));
|
|
6109
6420
|
}
|
|
6421
|
+
setMarketSyncDialog(null);
|
|
6422
|
+
await Promise.all([
|
|
6423
|
+
reloadCatalog(),
|
|
6424
|
+
reloadGroups(),
|
|
6425
|
+
reloadSources()
|
|
6426
|
+
]);
|
|
6427
|
+
checkMarket();
|
|
6428
|
+
} catch (error) {
|
|
6429
|
+
shared.fail(errorMessage(error));
|
|
6430
|
+
} finally {
|
|
6431
|
+
setSyncBusy(false);
|
|
6110
6432
|
}
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
checkMarket
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6433
|
+
}, [
|
|
6434
|
+
api,
|
|
6435
|
+
marketSyncDialog,
|
|
6436
|
+
reloadCatalog,
|
|
6437
|
+
reloadGroups,
|
|
6438
|
+
reloadSources,
|
|
6439
|
+
checkMarket,
|
|
6440
|
+
shared
|
|
6441
|
+
]),
|
|
6442
|
+
updateAll: (0, react.useCallback)(async () => {
|
|
6443
|
+
setUpdateAllDialog(false);
|
|
6444
|
+
shared.setBatchBusy(true);
|
|
6445
|
+
shared.clearFail();
|
|
6446
|
+
const failures = [];
|
|
6447
|
+
let done = 0;
|
|
6448
|
+
try {
|
|
6449
|
+
for (const [repo, check] of Object.entries(sourceCheck)) {
|
|
6450
|
+
if (!check.changed || check.updated.length === 0) continue;
|
|
6451
|
+
try {
|
|
6452
|
+
const result = await api.syncSource(repo, check.updated);
|
|
6453
|
+
done += 1;
|
|
6454
|
+
if (result.failed.length > 0) failures.push(tt("market.updateAllItem", {
|
|
6455
|
+
repo,
|
|
6456
|
+
count: result.failed.length
|
|
6457
|
+
}) + ": " + result.failed.map((item) => item.name + ": " + item.error).join("; "));
|
|
6458
|
+
} catch (error) {
|
|
6459
|
+
failures.push(repo + ": " + errorMessage(error));
|
|
6460
|
+
}
|
|
6461
|
+
}
|
|
6462
|
+
await Promise.all([
|
|
6463
|
+
reloadCatalog(),
|
|
6464
|
+
reloadGroups(),
|
|
6465
|
+
reloadSources()
|
|
6466
|
+
]);
|
|
6467
|
+
await checkSources();
|
|
6468
|
+
checkMarket();
|
|
6469
|
+
if (failures.length > 0) shared.fail(failures.join("\n"));
|
|
6470
|
+
else if (done > 0) shared.succeed(tt("market.updateAllDone", { count: done }));
|
|
6471
|
+
} finally {
|
|
6472
|
+
shared.setBatchBusy(false);
|
|
6473
|
+
}
|
|
6474
|
+
}, [
|
|
6475
|
+
api,
|
|
6476
|
+
sourceCheck,
|
|
6477
|
+
checkSources,
|
|
6478
|
+
checkMarket,
|
|
6479
|
+
reloadCatalog,
|
|
6480
|
+
reloadGroups,
|
|
6481
|
+
reloadSources,
|
|
6482
|
+
shared
|
|
6483
|
+
])
|
|
6484
|
+
};
|
|
6485
|
+
}
|
|
6486
|
+
//#endregion
|
|
6487
|
+
//#region src/client/panel/useSkillHub.ts
|
|
6488
|
+
/**
|
|
6489
|
+
* useSkillHub — 面板的状态聚合根:只持有跨域共享的通知状态与纯视图状态,
|
|
6490
|
+
* 各域状态与动作分散在 ./hooks/*,这里按依赖顺序组装(meta → 目录 →
|
|
6491
|
+
* 分组 → 来源 → 市场),轮询与派生组装在此。返回形状与拆分前完全一致,
|
|
6492
|
+
* 所有视图无需改动。All hooks run unconditionally at the top, so the panel
|
|
6493
|
+
* may early-return for the detail and tag-editor views without violating
|
|
6494
|
+
* the rules of hooks.
|
|
6495
|
+
*/
|
|
6496
|
+
function useSkillHub(api) {
|
|
6497
|
+
const [loadError, setLoadError] = (0, react.useState)(null);
|
|
6498
|
+
/** Green success banner (create finished); shown outside the closing form. */
|
|
6499
|
+
const [successBanner, setSuccessBanner] = (0, react.useState)(null);
|
|
6500
|
+
const [batchBusy, setBatchBusy] = (0, react.useState)(false);
|
|
6501
|
+
const [tagBusy, setTagBusy] = (0, react.useState)(false);
|
|
6502
|
+
const shared = (0, react.useMemo)(() => ({
|
|
6503
|
+
fail: (message) => setLoadError(message),
|
|
6504
|
+
clearFail: () => setLoadError(null),
|
|
6505
|
+
succeed: (message) => setSuccessBanner(message),
|
|
6506
|
+
setTagBusy,
|
|
6507
|
+
setBatchBusy
|
|
6508
|
+
}), []);
|
|
6509
|
+
const [tab, setTab] = (0, react.useState)("sources");
|
|
6510
|
+
const [skillView, setSkillView] = (0, react.useState)("groups");
|
|
6511
|
+
const [sourceFilter, setSourceFilter] = (0, react.useState)("all");
|
|
6512
|
+
/** 分组视图里收起的分组(key 为 tag:<id>、col:<name> 或 project 树键)。技能总数 >80 时首次加载自动折叠 personal。 */
|
|
6513
|
+
const [collapsedGroups, setCollapsedGroups] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
6514
|
+
/** 项目级三级树里已细分(按 .dsh/.agents)的项目键。 */
|
|
6515
|
+
const [subdividedProjects, setSubdividedProjects] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
6516
|
+
const [showLegend, setShowLegend] = (0, react.useState)(false);
|
|
6517
|
+
const [editMode, setEditMode] = (0, react.useState)(false);
|
|
6518
|
+
const toggleGroupCollapse = (0, react.useCallback)((key) => {
|
|
6519
|
+
setCollapsedGroups((previous) => {
|
|
6520
|
+
const next = new Set(previous);
|
|
6521
|
+
if (next.has(key)) next.delete(key);
|
|
6522
|
+
else next.add(key);
|
|
6523
|
+
return next;
|
|
6524
|
+
});
|
|
6138
6525
|
}, []);
|
|
6139
|
-
const
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
text: tt("form.error") + errorMessage(error)
|
|
6159
|
-
});
|
|
6160
|
-
} finally {
|
|
6161
|
-
setFormBusy(false);
|
|
6162
|
-
}
|
|
6163
|
-
}, [
|
|
6164
|
-
api,
|
|
6165
|
-
formName,
|
|
6166
|
-
formDesc,
|
|
6167
|
-
formRoot,
|
|
6168
|
-
load
|
|
6169
|
-
]);
|
|
6170
|
-
const normalized = search.trim().toLocaleLowerCase();
|
|
6526
|
+
const toggleSubdivide = (0, react.useCallback)((key) => {
|
|
6527
|
+
setSubdividedProjects((previous) => {
|
|
6528
|
+
const next = new Set(previous);
|
|
6529
|
+
if (next.has(key)) next.delete(key);
|
|
6530
|
+
else next.add(key);
|
|
6531
|
+
return next;
|
|
6532
|
+
});
|
|
6533
|
+
}, []);
|
|
6534
|
+
const collapsePersonal = (0, react.useCallback)(() => {
|
|
6535
|
+
setCollapsedGroups((previous) => new Set(previous).add("uncategorized-source"));
|
|
6536
|
+
}, []);
|
|
6537
|
+
const clearSourceFilter = (0, react.useCallback)(() => {
|
|
6538
|
+
setSourceFilter("all");
|
|
6539
|
+
}, []);
|
|
6540
|
+
const meta = useMetaFlow(api);
|
|
6541
|
+
const catalogFlow = useCatalogFlow(api, meta.uses, shared, collapsePersonal, clearSourceFilter);
|
|
6542
|
+
const groupFlow = useGroupFlow(api, shared, catalogFlow.batchToggleNames, catalogFlow.actionNames);
|
|
6543
|
+
const sourceFlow = useSourceFlow(api, shared, catalogFlow.load, groupFlow.loadGroups);
|
|
6544
|
+
const marketFlow = useMarketFlow(api, shared, catalogFlow.load, groupFlow.loadGroups, sourceFlow.loadSources, sourceFlow.checkSources, sourceFlow.sourceCheck);
|
|
6171
6545
|
/** skillName → origin repo(无来源记录的技能不在此映射中,筛选中视为 private)。 */
|
|
6172
|
-
const origins = groupsState?.origins ?? {};
|
|
6546
|
+
const origins = groupFlow.groupsState?.origins ?? {};
|
|
6173
6547
|
/** 来源筛选选项:来源记录中的仓库(排序)+ 末尾的「个人」;没有技能的仓库不列。 */
|
|
6174
6548
|
const sourceOptions = (0, react.useMemo)(() => {
|
|
6175
|
-
const skills = catalog?.skills ?? [];
|
|
6549
|
+
const skills = catalogFlow.catalog?.skills ?? [];
|
|
6176
6550
|
const repos = [...new Set(skills.map((skill) => origins[skill.name]).filter((repo) => repo !== void 0))].sort();
|
|
6177
6551
|
const hasPrivate = skills.some((skill) => origins[skill.name] === void 0 && !isProjectSource(skill.source));
|
|
6178
6552
|
return [...repos, ...hasPrivate ? [PRIVATE_SOURCE] : []];
|
|
6179
|
-
}, [catalog, origins]);
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6553
|
+
}, [catalogFlow.catalog, origins]);
|
|
6554
|
+
(0, react.useEffect)(() => {
|
|
6555
|
+
catalogFlow.load();
|
|
6556
|
+
meta.loadUses();
|
|
6557
|
+
groupFlow.loadGroups();
|
|
6558
|
+
sourceFlow.loadSources();
|
|
6559
|
+
meta.loadConfig();
|
|
6560
|
+
const fast = window.setInterval(() => {
|
|
6561
|
+
catalogFlow.load();
|
|
6562
|
+
}, 5e3);
|
|
6563
|
+
const slow = window.setInterval(() => {
|
|
6564
|
+
meta.loadUses();
|
|
6565
|
+
groupFlow.loadGroups();
|
|
6566
|
+
sourceFlow.loadSources();
|
|
6567
|
+
meta.loadConfig();
|
|
6568
|
+
}, 6e4);
|
|
6569
|
+
return () => {
|
|
6570
|
+
window.clearInterval(fast);
|
|
6571
|
+
window.clearInterval(slow);
|
|
6572
|
+
};
|
|
6573
|
+
}, [
|
|
6574
|
+
catalogFlow.load,
|
|
6575
|
+
meta.loadUses,
|
|
6576
|
+
groupFlow.loadGroups,
|
|
6577
|
+
sourceFlow.loadSources,
|
|
6578
|
+
meta.loadConfig
|
|
6189
6579
|
]);
|
|
6190
|
-
|
|
6191
|
-
|
|
6580
|
+
(0, react.useEffect)(() => {
|
|
6581
|
+
if (!shouldAutoCheck()) return;
|
|
6582
|
+
markAutoChecked();
|
|
6583
|
+
meta.checkUpdate();
|
|
6584
|
+
sourceFlow.checkSources();
|
|
6585
|
+
marketFlow.checkMarket();
|
|
6586
|
+
}, []);
|
|
6192
6587
|
return {
|
|
6193
|
-
catalog,
|
|
6194
|
-
loading,
|
|
6588
|
+
catalog: catalogFlow.catalog,
|
|
6589
|
+
loading: catalogFlow.loading,
|
|
6195
6590
|
loadError,
|
|
6196
6591
|
successBanner,
|
|
6197
|
-
updateState,
|
|
6198
|
-
repoDiscoverState,
|
|
6199
|
-
scanningRepo,
|
|
6200
|
-
repoSelected,
|
|
6201
|
-
repoImporting,
|
|
6202
|
-
repoResult,
|
|
6203
|
-
importJobId,
|
|
6204
|
-
search,
|
|
6205
|
-
workspace,
|
|
6206
|
-
detail,
|
|
6207
|
-
detailLoading,
|
|
6208
|
-
busyNames,
|
|
6592
|
+
updateState: meta.updateState,
|
|
6593
|
+
repoDiscoverState: marketFlow.repoDiscoverState,
|
|
6594
|
+
scanningRepo: marketFlow.scanningRepo,
|
|
6595
|
+
repoSelected: marketFlow.repoSelected,
|
|
6596
|
+
repoImporting: marketFlow.repoImporting,
|
|
6597
|
+
repoResult: marketFlow.repoResult,
|
|
6598
|
+
importJobId: marketFlow.importJobId,
|
|
6599
|
+
search: catalogFlow.search,
|
|
6600
|
+
workspace: catalogFlow.workspace,
|
|
6601
|
+
detail: catalogFlow.detail,
|
|
6602
|
+
detailLoading: catalogFlow.detailLoading,
|
|
6603
|
+
busyNames: catalogFlow.busyNames,
|
|
6209
6604
|
batchBusy,
|
|
6210
|
-
showForm,
|
|
6211
|
-
formName,
|
|
6212
|
-
formDesc,
|
|
6213
|
-
formRoot,
|
|
6214
|
-
formBusy,
|
|
6215
|
-
formMessage,
|
|
6216
|
-
uses,
|
|
6217
|
-
hubConfig,
|
|
6605
|
+
showForm: catalogFlow.showForm,
|
|
6606
|
+
formName: catalogFlow.formName,
|
|
6607
|
+
formDesc: catalogFlow.formDesc,
|
|
6608
|
+
formRoot: catalogFlow.formRoot,
|
|
6609
|
+
formBusy: catalogFlow.formBusy,
|
|
6610
|
+
formMessage: catalogFlow.formMessage,
|
|
6611
|
+
uses: meta.uses,
|
|
6612
|
+
hubConfig: meta.hubConfig,
|
|
6218
6613
|
tab,
|
|
6219
6614
|
skillView,
|
|
6220
6615
|
sourceFilter,
|
|
6221
|
-
invocationFilter,
|
|
6222
|
-
sortKey,
|
|
6223
|
-
marketState,
|
|
6224
|
-
marketCheck,
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6616
|
+
invocationFilter: catalogFlow.invocationFilter,
|
|
6617
|
+
sortKey: catalogFlow.sortKey,
|
|
6618
|
+
marketState: marketFlow.marketState,
|
|
6619
|
+
marketCheck: marketFlow.marketCheck,
|
|
6620
|
+
marketStats: marketFlow.marketStats,
|
|
6621
|
+
branchChoice: marketFlow.branchChoice,
|
|
6622
|
+
branchBusy: marketFlow.branchBusy,
|
|
6623
|
+
marketSyncDialog: marketFlow.marketSyncDialog,
|
|
6624
|
+
syncingMarket: marketFlow.syncingMarket,
|
|
6625
|
+
syncBusy: marketFlow.syncBusy,
|
|
6626
|
+
newSourceName: marketFlow.newSourceName,
|
|
6627
|
+
groupsState: groupFlow.groupsState,
|
|
6628
|
+
sourcesState: sourceFlow.sourcesState,
|
|
6629
|
+
sourceCheck: sourceFlow.sourceCheck,
|
|
6630
|
+
checkingSource: sourceFlow.checkingSource,
|
|
6631
|
+
syncingSource: sourceFlow.syncingSource,
|
|
6632
|
+
conflictDialog: groupFlow.conflictDialog,
|
|
6633
|
+
confirmDialog: sourceFlow.confirmDialog,
|
|
6634
|
+
deleteSkillDialog: sourceFlow.deleteSkillDialog,
|
|
6635
|
+
deleteGroupDialog: sourceFlow.deleteGroupDialog,
|
|
6636
|
+
confirmClearTrash: sourceFlow.confirmClearTrash,
|
|
6637
|
+
updateAllDialog: marketFlow.updateAllDialog,
|
|
6638
|
+
editingTag: groupFlow.editingTag,
|
|
6639
|
+
editName: groupFlow.editName,
|
|
6640
|
+
membersDraft: groupFlow.membersDraft,
|
|
6641
|
+
newTagName: groupFlow.newTagName,
|
|
6246
6642
|
tagBusy,
|
|
6247
|
-
editSearch,
|
|
6643
|
+
editSearch: groupFlow.editSearch,
|
|
6248
6644
|
collapsedGroups,
|
|
6249
6645
|
subdividedProjects,
|
|
6250
6646
|
showLegend,
|
|
6251
6647
|
editMode,
|
|
6252
|
-
versionDialog,
|
|
6253
|
-
versionBusy,
|
|
6254
|
-
actionNames,
|
|
6255
|
-
viewNames,
|
|
6256
|
-
normalized,
|
|
6648
|
+
versionDialog: marketFlow.versionDialog,
|
|
6649
|
+
versionBusy: marketFlow.versionBusy,
|
|
6650
|
+
actionNames: catalogFlow.actionNames,
|
|
6651
|
+
viewNames: catalogFlow.viewNames,
|
|
6652
|
+
normalized: catalogFlow.normalized,
|
|
6257
6653
|
origins,
|
|
6258
6654
|
sourceOptions,
|
|
6259
|
-
filtered,
|
|
6260
|
-
sorted:
|
|
6261
|
-
|
|
6262
|
-
sortKey,
|
|
6263
|
-
uses
|
|
6264
|
-
]),
|
|
6265
|
-
shortenedCount,
|
|
6655
|
+
filtered: catalogFlow.filtered,
|
|
6656
|
+
sorted: catalogFlow.sorted,
|
|
6657
|
+
shortenedCount: catalogFlow.shortenedCount,
|
|
6266
6658
|
setLoadError,
|
|
6267
6659
|
setSuccessBanner,
|
|
6268
|
-
setSearch,
|
|
6269
|
-
setWorkspace,
|
|
6270
|
-
setDetail,
|
|
6271
|
-
setShowForm,
|
|
6272
|
-
setFormName,
|
|
6273
|
-
setFormDesc,
|
|
6274
|
-
setFormRoot,
|
|
6275
|
-
setFormMessage,
|
|
6276
|
-
setRepoSelected,
|
|
6660
|
+
setSearch: catalogFlow.setSearch,
|
|
6661
|
+
setWorkspace: catalogFlow.setWorkspace,
|
|
6662
|
+
setDetail: catalogFlow.setDetail,
|
|
6663
|
+
setShowForm: catalogFlow.setShowForm,
|
|
6664
|
+
setFormName: catalogFlow.setFormName,
|
|
6665
|
+
setFormDesc: catalogFlow.setFormDesc,
|
|
6666
|
+
setFormRoot: catalogFlow.setFormRoot,
|
|
6667
|
+
setFormMessage: catalogFlow.setFormMessage,
|
|
6668
|
+
setRepoSelected: marketFlow.setRepoSelected,
|
|
6277
6669
|
setTab,
|
|
6278
6670
|
setSkillView,
|
|
6279
6671
|
setSourceFilter,
|
|
6280
|
-
setInvocationFilter,
|
|
6281
|
-
setSortKey,
|
|
6282
|
-
setBranchChoice,
|
|
6283
|
-
setMarketSyncDialog,
|
|
6284
|
-
setNewSourceName,
|
|
6285
|
-
setConflictDialog,
|
|
6286
|
-
setConfirmDialog,
|
|
6287
|
-
setDeleteSkillDialog,
|
|
6288
|
-
setDeleteGroupDialog,
|
|
6289
|
-
setConfirmClearTrash,
|
|
6290
|
-
setUpdateAllDialog,
|
|
6291
|
-
setEditingTag,
|
|
6292
|
-
setEditName,
|
|
6293
|
-
setMembersDraft,
|
|
6294
|
-
setNewTagName,
|
|
6295
|
-
setEditSearch,
|
|
6672
|
+
setInvocationFilter: catalogFlow.setInvocationFilter,
|
|
6673
|
+
setSortKey: catalogFlow.setSortKey,
|
|
6674
|
+
setBranchChoice: marketFlow.setBranchChoice,
|
|
6675
|
+
setMarketSyncDialog: marketFlow.setMarketSyncDialog,
|
|
6676
|
+
setNewSourceName: marketFlow.setNewSourceName,
|
|
6677
|
+
setConflictDialog: groupFlow.setConflictDialog,
|
|
6678
|
+
setConfirmDialog: sourceFlow.setConfirmDialog,
|
|
6679
|
+
setDeleteSkillDialog: sourceFlow.setDeleteSkillDialog,
|
|
6680
|
+
setDeleteGroupDialog: sourceFlow.setDeleteGroupDialog,
|
|
6681
|
+
setConfirmClearTrash: sourceFlow.setConfirmClearTrash,
|
|
6682
|
+
setUpdateAllDialog: marketFlow.setUpdateAllDialog,
|
|
6683
|
+
setEditingTag: groupFlow.setEditingTag,
|
|
6684
|
+
setEditName: groupFlow.setEditName,
|
|
6685
|
+
setMembersDraft: groupFlow.setMembersDraft,
|
|
6686
|
+
setNewTagName: groupFlow.setNewTagName,
|
|
6687
|
+
setEditSearch: groupFlow.setEditSearch,
|
|
6296
6688
|
setShowLegend,
|
|
6297
6689
|
setEditMode,
|
|
6298
|
-
setVersionDialog,
|
|
6690
|
+
setVersionDialog: marketFlow.setVersionDialog,
|
|
6299
6691
|
toggleGroupCollapse,
|
|
6300
6692
|
toggleSubdivide,
|
|
6301
|
-
checkUpdate,
|
|
6302
|
-
loadMarket,
|
|
6303
|
-
openDetail,
|
|
6304
|
-
toggle,
|
|
6305
|
-
enableDisabled,
|
|
6306
|
-
batchToggleNames,
|
|
6307
|
-
toggleGroup,
|
|
6308
|
-
resolveConflict,
|
|
6309
|
-
runConfirmed,
|
|
6310
|
-
checkSources,
|
|
6311
|
-
requestSync,
|
|
6312
|
-
requestDelete,
|
|
6313
|
-
restoreTrash,
|
|
6314
|
-
clearTrash,
|
|
6315
|
-
fixingPaths,
|
|
6316
|
-
fixDiagnostic,
|
|
6317
|
-
clearListFilters:
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
confirmMarketSync,
|
|
6346
|
-
updateAll,
|
|
6347
|
-
create
|
|
6693
|
+
checkUpdate: meta.checkUpdate,
|
|
6694
|
+
loadMarket: marketFlow.loadMarket,
|
|
6695
|
+
openDetail: catalogFlow.openDetail,
|
|
6696
|
+
toggle: catalogFlow.toggle,
|
|
6697
|
+
enableDisabled: catalogFlow.enableDisabled,
|
|
6698
|
+
batchToggleNames: catalogFlow.batchToggleNames,
|
|
6699
|
+
toggleGroup: groupFlow.toggleGroup,
|
|
6700
|
+
resolveConflict: groupFlow.resolveConflict,
|
|
6701
|
+
runConfirmed: sourceFlow.runConfirmed,
|
|
6702
|
+
checkSources: sourceFlow.checkSources,
|
|
6703
|
+
requestSync: sourceFlow.requestSync,
|
|
6704
|
+
requestDelete: sourceFlow.requestDelete,
|
|
6705
|
+
restoreTrash: sourceFlow.restoreTrash,
|
|
6706
|
+
clearTrash: sourceFlow.clearTrash,
|
|
6707
|
+
fixingPaths: catalogFlow.fixingPaths,
|
|
6708
|
+
fixDiagnostic: catalogFlow.fixDiagnostic,
|
|
6709
|
+
clearListFilters: catalogFlow.clearListFilters,
|
|
6710
|
+
openVersionDialog: marketFlow.openVersionDialog,
|
|
6711
|
+
confirmVersionDialog: marketFlow.confirmVersionDialog,
|
|
6712
|
+
requestDeleteSkill: sourceFlow.requestDeleteSkill,
|
|
6713
|
+
runDeleteSkill: sourceFlow.runDeleteSkill,
|
|
6714
|
+
requestDeleteGroup: sourceFlow.requestDeleteGroup,
|
|
6715
|
+
runDeleteGroup: sourceFlow.runDeleteGroup,
|
|
6716
|
+
createTag: groupFlow.createTag,
|
|
6717
|
+
deleteTag: groupFlow.deleteTag,
|
|
6718
|
+
saveTag: groupFlow.saveTag,
|
|
6719
|
+
reorderTags: groupFlow.reorderTags,
|
|
6720
|
+
reorderCollections: groupFlow.reorderCollections,
|
|
6721
|
+
reorderSourceGroups: groupFlow.reorderSourceGroups,
|
|
6722
|
+
addSource: marketFlow.addSource,
|
|
6723
|
+
addMarketSource: marketFlow.addMarketSource,
|
|
6724
|
+
removeMarketSource: marketFlow.removeMarketSource,
|
|
6725
|
+
scanRepo: marketFlow.scanRepo,
|
|
6726
|
+
confirmBranchChoice: marketFlow.confirmBranchChoice,
|
|
6727
|
+
toggleRepoSelected: marketFlow.toggleRepoSelected,
|
|
6728
|
+
importRepo: marketFlow.importRepo,
|
|
6729
|
+
cancelImport: marketFlow.cancelImport,
|
|
6730
|
+
clearScan: marketFlow.clearScan,
|
|
6731
|
+
checkMarket: marketFlow.checkMarket,
|
|
6732
|
+
loadMarketStats: marketFlow.loadMarketStats,
|
|
6733
|
+
syncMarketSource: marketFlow.syncMarketSource,
|
|
6734
|
+
confirmMarketSync: marketFlow.confirmMarketSync,
|
|
6735
|
+
updateAll: marketFlow.updateAll,
|
|
6736
|
+
create: catalogFlow.create
|
|
6348
6737
|
};
|
|
6349
6738
|
}
|
|
6350
6739
|
//#endregion
|