dsh-skill-hub 0.3.5 → 0.3.7
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/lib/client.js +1238 -509
- package/lib/client.js.map +1 -1
- package/lib/index.js +401 -47
- package/lib/types/client/SkillHubSettingsCard.d.ts +0 -2
- package/lib/types/client/api.d.ts +5 -1
- package/lib/types/client/helpers.d.ts +10 -0
- package/lib/types/client/locales.d.ts +30 -0
- package/lib/types/client/panel/DisabledRow.d.ts +2 -0
- package/lib/types/client/panel/SkillDetailView.d.ts +5 -1
- package/lib/types/client/panel/dialogs.d.ts +19 -0
- package/lib/types/client/panel/useSkillHub.d.ts +12 -1
- package/lib/types/client/settings-card.d.ts +0 -17
- package/lib/types/client/settings-form.d.ts +0 -1
- package/lib/types/protocol.d.ts +40 -17
- package/lib/types/repo.d.ts +4 -6
- package/lib/types/skillfs.d.ts +13 -0
- package/package.json +30 -28
- package/src/client/SkillHubSettingsCard.tsx +0 -2
- package/src/client/api.ts +18 -0
- package/src/client/helpers.ts +40 -0
- package/src/client/index.tsx +9 -5
- package/src/client/locales.ts +60 -0
- package/src/client/panel/DisabledRow.tsx +22 -7
- package/src/client/panel/MarketView.tsx +8 -1
- package/src/client/panel/ScenesView.tsx +1 -1
- package/src/client/panel/SkillDetailView.tsx +35 -11
- package/src/client/panel/SkillHubPanel.tsx +42 -4
- package/src/client/panel/SkillRow.tsx +5 -2
- package/src/client/panel/SourcesView.tsx +2 -17
- package/src/client/panel/dialogs.tsx +61 -0
- package/src/client/panel/panel.module.css +3 -10
- package/src/client/panel/useSkillHub.ts +97 -12
- package/src/client/settings-card.tsx +0 -42
- package/src/client/settings-form.ts +1 -5
- package/src/client/slash-dots.tsx +3 -2
- package/src/protocol.ts +42 -8
- package/src/repo.ts +71 -29
- package/src/routes.test.ts +22 -0
- package/src/routes.ts +181 -10
- package/src/skillfs.ts +188 -3
package/lib/client.js
CHANGED
|
@@ -46,7 +46,9 @@ window.__ModuleLoader__.load({
|
|
|
46
46
|
sourceSync: "/api/skill-hub/sources/sync",
|
|
47
47
|
sourceDelete: "/api/skill-hub/sources/delete",
|
|
48
48
|
sourceRestore: "/api/skill-hub/sources/restore",
|
|
49
|
-
sourceTrashClear: "/api/skill-hub/sources/trash/clear"
|
|
49
|
+
sourceTrashClear: "/api/skill-hub/sources/trash/clear",
|
|
50
|
+
diagnosticFix: "/api/skill-hub/diagnostic/fix",
|
|
51
|
+
marketSourceVersions: "/api/skill-hub/market/source/versions"
|
|
50
52
|
};
|
|
51
53
|
/** 项目级技能来源(它们有 workspace 归属,不属于「个人」组)。 */
|
|
52
54
|
function isProjectSource(source) {
|
|
@@ -177,6 +179,10 @@ window.__ModuleLoader__.load({
|
|
|
177
179
|
body: JSON.stringify({ repo })
|
|
178
180
|
}));
|
|
179
181
|
}
|
|
182
|
+
/** Releases + branches of a market source for the version picker. */
|
|
183
|
+
async marketSourceVersions(repo) {
|
|
184
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.marketSourceVersions + "?repo=" + encodeURIComponent(repo)));
|
|
185
|
+
}
|
|
180
186
|
/** Pin a market source to an explicit ref (branch picker). */
|
|
181
187
|
async setMarketSourceRef(repo, ref) {
|
|
182
188
|
return readJson(await fetchWithTimeout(SKILL_HUB_API.marketSourceRef, {
|
|
@@ -338,6 +344,14 @@ window.__ModuleLoader__.load({
|
|
|
338
344
|
async clearTrash() {
|
|
339
345
|
return readJson(await fetchWithTimeout(SKILL_HUB_API.sourceTrashClear, { method: "POST" }));
|
|
340
346
|
}
|
|
347
|
+
/** Auto-fix a fixable diagnostic (e.g. unquoted colon). */
|
|
348
|
+
async fixDiagnostic(path) {
|
|
349
|
+
return readJson(await fetchWithTimeout(SKILL_HUB_API.diagnosticFix, {
|
|
350
|
+
method: "POST",
|
|
351
|
+
headers: { "content-type": "application/json" },
|
|
352
|
+
body: JSON.stringify({ path })
|
|
353
|
+
}));
|
|
354
|
+
}
|
|
341
355
|
};
|
|
342
356
|
//#endregion
|
|
343
357
|
//#region src/client/locales.ts
|
|
@@ -407,12 +421,22 @@ window.__ModuleLoader__.load({
|
|
|
407
421
|
"market.branchTitle": "选择分支",
|
|
408
422
|
"market.branchHint": "该仓库没有正式发布,选择要跟踪的分支(默认为 main):",
|
|
409
423
|
"market.branchConfirm": "确定",
|
|
424
|
+
"market.versionTitle": "切换跟踪版本",
|
|
425
|
+
"market.versionText": "选择 {repo} 要跟踪的版本,切换后建议重新扫描并同步已装技能",
|
|
426
|
+
"market.versionCurrent": "当前 {ref}",
|
|
427
|
+
"market.versionReleases": "发布版",
|
|
428
|
+
"market.versionBranches": "分支",
|
|
429
|
+
"market.versionCustom": "自定义(tag / 分支 / commit)",
|
|
430
|
+
"market.versionConfirm": "切换到 {ref}",
|
|
431
|
+
"market.versionHint": "点击切换跟踪版本",
|
|
432
|
+
"market.unpinned": "未定版",
|
|
410
433
|
"market.syncTitle": "同步本地技能",
|
|
411
434
|
"market.syncText": "市场源已对齐到 {ref},要同步更新以下本地技能吗?",
|
|
412
435
|
"market.syncNone": "该来源还没有导入本地技能。",
|
|
413
436
|
"market.syncCancel": "跳过",
|
|
414
437
|
"market.removeHint": "移除源不影响已安装的技能。",
|
|
415
438
|
"repo.ready": "发现 {count} 个可导入技能",
|
|
439
|
+
"repo.truncated": "⚠ 仓库过大,仅展示部分技能(GitHub 截断),可缩小仓库或指定子目录",
|
|
416
440
|
"repo.empty": "没有找到可导入技能。",
|
|
417
441
|
"repo.selectAll": "全选",
|
|
418
442
|
"repo.clearAll": "清空",
|
|
@@ -485,6 +509,26 @@ window.__ModuleLoader__.load({
|
|
|
485
509
|
"row.enable": "启用",
|
|
486
510
|
"row.delete": "移入回收站",
|
|
487
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} 条,悬浮看全文",
|
|
488
532
|
"delete.confirmTitle": "移入回收站?",
|
|
489
533
|
"delete.confirmText": "确定把「{name}」移入回收站吗?可随时恢复。",
|
|
490
534
|
"delete.confirm": "移入回收站",
|
|
@@ -632,12 +676,22 @@ window.__ModuleLoader__.load({
|
|
|
632
676
|
"market.branchTitle": "Choose a branch",
|
|
633
677
|
"market.branchHint": "This repo has no releases. Pick a branch to track (main by default):",
|
|
634
678
|
"market.branchConfirm": "Confirm",
|
|
679
|
+
"market.versionTitle": "Switch tracked version",
|
|
680
|
+
"market.versionText": "Pick the version for {repo} to track; rescan and sync installed skills afterwards",
|
|
681
|
+
"market.versionCurrent": "current {ref}",
|
|
682
|
+
"market.versionReleases": "Releases",
|
|
683
|
+
"market.versionBranches": "Branches",
|
|
684
|
+
"market.versionCustom": "Custom (tag / branch / commit)",
|
|
685
|
+
"market.versionConfirm": "Switch to {ref}",
|
|
686
|
+
"market.versionHint": "Click to switch the tracked version",
|
|
687
|
+
"market.unpinned": "unpinned",
|
|
635
688
|
"market.syncTitle": "Update local skills",
|
|
636
689
|
"market.syncText": "Market source aligned to {ref}. Update these local skills?",
|
|
637
690
|
"market.syncNone": "No local skills tracked from this repo yet.",
|
|
638
691
|
"market.syncCancel": "Skip",
|
|
639
692
|
"market.removeHint": "Removing a source does not affect installed skills.",
|
|
640
693
|
"repo.ready": "{count} importable skills found",
|
|
694
|
+
"repo.truncated": "⚠ Repo too large, only partial skills shown (GitHub truncated)",
|
|
641
695
|
"repo.empty": "No importable skills found.",
|
|
642
696
|
"repo.selectAll": "Select all",
|
|
643
697
|
"repo.clearAll": "Clear",
|
|
@@ -710,6 +764,26 @@ window.__ModuleLoader__.load({
|
|
|
710
764
|
"row.enable": "Enable",
|
|
711
765
|
"row.delete": "Move to trash",
|
|
712
766
|
"row.open": "View {name} details (Enter)",
|
|
767
|
+
"row.duplicate": "duplicate",
|
|
768
|
+
"row.duplicateHint": "Multiple sources provide this name; the highest-priority one is shown",
|
|
769
|
+
"row.copy": "Copy",
|
|
770
|
+
"row.copyMention": "Copy $mention",
|
|
771
|
+
"row.copied": "Copied",
|
|
772
|
+
"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
|
+
"filter.invocation": "Invocation",
|
|
781
|
+
"filter.invocationAll": "All",
|
|
782
|
+
"filter.modelOnly": "Model",
|
|
783
|
+
"filter.userOnly": "User",
|
|
784
|
+
"filter.showing": "Showing {shown}/{total} (enabled){filtered}",
|
|
785
|
+
"filter.filteredSuffix": " · filtered",
|
|
786
|
+
"filter.shortened": "{count} short descriptions, hover for full text",
|
|
713
787
|
"delete.confirmTitle": "Move to trash?",
|
|
714
788
|
"delete.confirmText": "Move \"{name}\" to trash? It can be restored anytime.",
|
|
715
789
|
"delete.confirm": "Move to trash",
|
|
@@ -846,6 +920,367 @@ window.__ModuleLoader__.load({
|
|
|
846
920
|
};
|
|
847
921
|
}
|
|
848
922
|
//#endregion
|
|
923
|
+
//#region src/client/grouping.ts
|
|
924
|
+
/**
|
|
925
|
+
* Derive a group switch view from its member names and the set of currently
|
|
926
|
+
* enabled skill names (catalog.skills). Members not in the enabled set count
|
|
927
|
+
* as disabled (catalog.disabled or absent read-only rows).
|
|
928
|
+
*/
|
|
929
|
+
function groupSwitchView(members, enabledNames) {
|
|
930
|
+
const enabled = [];
|
|
931
|
+
const disabled = [];
|
|
932
|
+
for (const name of members) if (enabledNames.has(name)) enabled.push(name);
|
|
933
|
+
else disabled.push(name);
|
|
934
|
+
return {
|
|
935
|
+
state: disabled.length === 0 ? "on" : enabled.length === 0 ? "off" : "mixed",
|
|
936
|
+
enabled,
|
|
937
|
+
disabled
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
/** Names of every group a skill belongs to (tags + collections). */
|
|
941
|
+
function groupNamesOf(name, tags, collections) {
|
|
942
|
+
const names = [];
|
|
943
|
+
for (const tag of tags) if (tag.skillNames.includes(name)) names.push(tag.name);
|
|
944
|
+
for (const collection of collections) if (collection.skillNames.includes(name)) names.push(collection.name);
|
|
945
|
+
return names;
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Members of a group that are currently enabled AND also belong to at least
|
|
949
|
+
* one other group — the set a "close" action must ask about.
|
|
950
|
+
*/
|
|
951
|
+
function conflictsOnClose(members, enabledNames, otherGroups) {
|
|
952
|
+
return members.filter((name) => {
|
|
953
|
+
if (!enabledNames.has(name)) return false;
|
|
954
|
+
return otherGroups.some((group) => group.members.includes(name));
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
/** Origin-repo filter value: skills with no source record (private skills). */
|
|
958
|
+
const PRIVATE_SOURCE = "private";
|
|
959
|
+
/**
|
|
960
|
+
* Apply the origin filter ('all' or a specific origin repo; skills without a
|
|
961
|
+
* source record count as PRIVATE_SOURCE). The origins map is the store's
|
|
962
|
+
* skillName → repo derivation, so filtering follows the tracked source
|
|
963
|
+
* records instead of the filesystem root a skill happens to live under.
|
|
964
|
+
* 项目级技能(有 workspace 归属)永远不算「个人」。
|
|
965
|
+
*/
|
|
966
|
+
function filterBySource(skills, source, origins) {
|
|
967
|
+
if (source === "all") return [...skills];
|
|
968
|
+
return skills.filter((skill) => {
|
|
969
|
+
if (isProjectSource(skill.source)) return false;
|
|
970
|
+
return (origins[skill.name] ?? "private") === source;
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Sort a skill list in place-safe copy order: name ascending, added
|
|
975
|
+
* descending (newest first, unknown addedAt last), or uses descending
|
|
976
|
+
* (most-called first). Unknown values always trail.
|
|
977
|
+
*/
|
|
978
|
+
function sortSkills(skills, key, getUses) {
|
|
979
|
+
const list = [...skills];
|
|
980
|
+
if (key === "name") list.sort((a, b) => a.name.localeCompare(b.name));
|
|
981
|
+
else if (key === "added") list.sort((a, b) => (b.addedAt ?? -Infinity) - (a.addedAt ?? -Infinity));
|
|
982
|
+
else if (key === "uses") list.sort((a, b) => (getUses?.(b.name) ?? 0) - (getUses?.(a.name) ?? 0));
|
|
983
|
+
return list;
|
|
984
|
+
}
|
|
985
|
+
/** Format an epoch-ms timestamp as a short relative time bucket. */
|
|
986
|
+
function formatRelativeTime(ms, now = Date.now()) {
|
|
987
|
+
const diff = Math.max(0, now - ms);
|
|
988
|
+
const minutes = Math.floor(diff / 6e4);
|
|
989
|
+
if (minutes < 1) return { key: "time.justNow" };
|
|
990
|
+
if (minutes < 60) return {
|
|
991
|
+
key: "time.minutesAgo",
|
|
992
|
+
value: minutes
|
|
993
|
+
};
|
|
994
|
+
const hours = Math.floor(minutes / 60);
|
|
995
|
+
if (hours < 24) return {
|
|
996
|
+
key: "time.hoursAgo",
|
|
997
|
+
value: hours
|
|
998
|
+
};
|
|
999
|
+
const days = Math.floor(hours / 24);
|
|
1000
|
+
if (days < 7) return {
|
|
1001
|
+
key: "time.daysAgo",
|
|
1002
|
+
value: days
|
|
1003
|
+
};
|
|
1004
|
+
return {
|
|
1005
|
+
key: "time.weeksAgo",
|
|
1006
|
+
value: Math.floor(days / 7)
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
//#endregion
|
|
1010
|
+
//#region src/client/helpers.ts
|
|
1011
|
+
/**
|
|
1012
|
+
* Shared panel helpers: the active-dictionary pick (document-language
|
|
1013
|
+
* based, family precedent) plus a small error-message extractor.
|
|
1014
|
+
*/
|
|
1015
|
+
/** Active dictionary, picked by the document language at call time. */
|
|
1016
|
+
function dictionary() {
|
|
1017
|
+
return (typeof document !== "undefined" ? document.documentElement.lang : "zh").toLowerCase().startsWith("en") ? { ...en } : { ...zh };
|
|
1018
|
+
}
|
|
1019
|
+
/** Translate a key with optional {name} template params (current language). */
|
|
1020
|
+
function tt(key, values) {
|
|
1021
|
+
return t(dictionary(), key, values);
|
|
1022
|
+
}
|
|
1023
|
+
/** Human-readable error text from an unknown thrown value. */
|
|
1024
|
+
function errorMessage(error) {
|
|
1025
|
+
if (error instanceof Error) return error.message;
|
|
1026
|
+
return String(error);
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Whether a displayName is worth showing beside the kebab-case name.
|
|
1030
|
+
* Pure case/punctuation variants (e.g. "Code Review" vs "code-review") are redundant.
|
|
1031
|
+
*/
|
|
1032
|
+
function isDisplayNameDistinct(name, displayName) {
|
|
1033
|
+
if (displayName === void 0) return false;
|
|
1034
|
+
const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff]+/g, "");
|
|
1035
|
+
return normalize(displayName) !== normalize(name);
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Copy text to the clipboard. Uses the async API with a positioned-textarea
|
|
1039
|
+
* fallback for insecure contexts. Resolves true only when the copy succeeded.
|
|
1040
|
+
*/
|
|
1041
|
+
async function copyTextToClipboard(text) {
|
|
1042
|
+
if (navigator.clipboard?.writeText !== void 0) try {
|
|
1043
|
+
await navigator.clipboard.writeText(text);
|
|
1044
|
+
return true;
|
|
1045
|
+
} catch {}
|
|
1046
|
+
try {
|
|
1047
|
+
const ta = document.createElement("textarea");
|
|
1048
|
+
ta.value = text;
|
|
1049
|
+
ta.setAttribute("readonly", "");
|
|
1050
|
+
ta.style.position = "fixed";
|
|
1051
|
+
ta.style.top = "0";
|
|
1052
|
+
ta.style.opacity = "0";
|
|
1053
|
+
document.body.appendChild(ta);
|
|
1054
|
+
ta.select();
|
|
1055
|
+
const ok = document.execCommand("copy");
|
|
1056
|
+
ta.remove();
|
|
1057
|
+
return ok;
|
|
1058
|
+
} catch {
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
//#endregion
|
|
1063
|
+
//#region src/client/panel/format.ts
|
|
1064
|
+
/** Model-invocable dot color default. Single source for the TS side; the
|
|
1065
|
+
* panel's CSS mirrors it via --hub-model (panel.module.css). */
|
|
1066
|
+
const DEFAULT_DOT_MODEL_COLOR = "#2f81f7";
|
|
1067
|
+
/** User-invocable dot color default. Single source for the TS side; the
|
|
1068
|
+
* panel's CSS mirrors it via --hub-user (panel.module.css). */
|
|
1069
|
+
const DEFAULT_DOT_USER_COLOR = "#3fb950";
|
|
1070
|
+
/** Dot inline style from the user-chosen color (undefined keeps the CSS default). */
|
|
1071
|
+
function dotStyle(color) {
|
|
1072
|
+
if (color === void 0) return void 0;
|
|
1073
|
+
return {
|
|
1074
|
+
background: color,
|
|
1075
|
+
borderColor: color
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
/** Localized relative-time text for a Unix-ms timestamp. */
|
|
1079
|
+
function relativeTimeText(ms) {
|
|
1080
|
+
const rt = formatRelativeTime(ms);
|
|
1081
|
+
return tt(rt.key, rt.value !== void 0 ? { value: rt.value } : void 0);
|
|
1082
|
+
}
|
|
1083
|
+
/** Localized absolute time for the detail meta (e.g. "2026/8/16 11:00:00"). */
|
|
1084
|
+
function formatDateTime(ms) {
|
|
1085
|
+
return new Date(ms).toLocaleString();
|
|
1086
|
+
}
|
|
1087
|
+
/** Short form of a commit SHA for display. */
|
|
1088
|
+
function shortSha(sha) {
|
|
1089
|
+
return sha.length > 7 ? sha.slice(0, 7) : sha;
|
|
1090
|
+
}
|
|
1091
|
+
//#endregion
|
|
1092
|
+
//#region src/client/slash-dots.tsx
|
|
1093
|
+
/** The core plugin's skill source identity on the '/' trigger. */
|
|
1094
|
+
const SKILL_SOURCE = {
|
|
1095
|
+
trigger: "/",
|
|
1096
|
+
name: "skill"
|
|
1097
|
+
};
|
|
1098
|
+
/** How long one catalog-derived modelInvocable map stays hot before refresh. */
|
|
1099
|
+
const MODEL_TTL_MS = 6e4;
|
|
1100
|
+
/**
|
|
1101
|
+
* Find the core `/skill` source through the runtime registry, or undefined.
|
|
1102
|
+
* The lookup never throws: a missing/reshaped registry just means no dots. Exported
|
|
1103
|
+
* for unit tests; the apply path only calls it indirectly through setupSkillSlashDots.
|
|
1104
|
+
* @param service - the ctx.inputTriggers service face.
|
|
1105
|
+
* @returns the registered skill source, or undefined.
|
|
1106
|
+
*/
|
|
1107
|
+
function findSkillSource(service) {
|
|
1108
|
+
const live = service.live;
|
|
1109
|
+
if (live?.sources === void 0) return void 0;
|
|
1110
|
+
return live.sources.find((source) => source.trigger === SKILL_SOURCE.trigger && source.name === SKILL_SOURCE.name);
|
|
1111
|
+
}
|
|
1112
|
+
/** One catalog-derived name → modelInvocable snapshot with a load timestamp. */
|
|
1113
|
+
let modelCache;
|
|
1114
|
+
/**
|
|
1115
|
+
* Clear the modelInvocable cache. Called on connection/reset so a fresh
|
|
1116
|
+
* catalog wins after reconnect; exported for deterministic unit tests.
|
|
1117
|
+
*/
|
|
1118
|
+
function resetModelCache() {
|
|
1119
|
+
modelCache = void 0;
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* Resolve name → modelInvocable from the hub's catalog, cached for
|
|
1123
|
+
* MODEL_TTL_MS. A failed load caches the failure briefly so a downed route
|
|
1124
|
+
* doesn't hammer the host on every keystroke; the returned map is empty then
|
|
1125
|
+
* (callers fall back to the model dot for unknown names).
|
|
1126
|
+
* @param api - the hub browser API.
|
|
1127
|
+
* @returns name → whether the model may call the skill.
|
|
1128
|
+
*/
|
|
1129
|
+
async function modelInvocableMap(api) {
|
|
1130
|
+
const now = Date.now();
|
|
1131
|
+
const cached = modelCache;
|
|
1132
|
+
if (cached !== void 0 && now - cached.at < MODEL_TTL_MS) return "failed" in cached ? /* @__PURE__ */ new Map() : cached.map;
|
|
1133
|
+
try {
|
|
1134
|
+
const catalog = await api.catalog();
|
|
1135
|
+
const map = new Map(catalog.skills.map((skill) => [skill.name, skill.invocation.modelInvocable]));
|
|
1136
|
+
modelCache = {
|
|
1137
|
+
at: now,
|
|
1138
|
+
map
|
|
1139
|
+
};
|
|
1140
|
+
return map;
|
|
1141
|
+
} catch (error) {
|
|
1142
|
+
console.error("[dsh-skill-hub] slash dot color lookup failed:", error);
|
|
1143
|
+
modelCache = {
|
|
1144
|
+
at: now,
|
|
1145
|
+
failed: true
|
|
1146
|
+
};
|
|
1147
|
+
return /* @__PURE__ */ new Map();
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* One menu-row dot element. Inline span so it needs no CSS module; the
|
|
1152
|
+
* candidate menu centers it inside its 16×16 leading icon slot.
|
|
1153
|
+
* @param color - the dot's background color.
|
|
1154
|
+
* @returns a React node (memory-only, never crosses the Host boundary).
|
|
1155
|
+
*/
|
|
1156
|
+
function dotIcon(color) {
|
|
1157
|
+
return (0, react.createElement)("span", {
|
|
1158
|
+
"aria-hidden": true,
|
|
1159
|
+
style: {
|
|
1160
|
+
display: "inline-block",
|
|
1161
|
+
width: 6,
|
|
1162
|
+
height: 6,
|
|
1163
|
+
borderRadius: 3,
|
|
1164
|
+
background: color,
|
|
1165
|
+
flex: "none"
|
|
1166
|
+
}
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Wrap the core skill source so every menu row carries the invocation dot.
|
|
1171
|
+
* Exported for unit tests; production wiring goes through setupSkillSlashDots.
|
|
1172
|
+
* @param source - the registered `/skill` source.
|
|
1173
|
+
* @param api - hub browser API for the modelInvocable lookup.
|
|
1174
|
+
* @param scope - hub settings scope for the dot colors.
|
|
1175
|
+
* @returns a disposer restoring the original candidates.
|
|
1176
|
+
*/
|
|
1177
|
+
/**
|
|
1178
|
+
* DOM 兜底:在 alpha.2 新版 MenuView(icon 仅枚举)下通过直接操作
|
|
1179
|
+
* 已渲染的 `[role="option"]` 列表注入彩色点,绕过 `icon` 限制。
|
|
1180
|
+
* 旧版仍走 `icon` 注入,此处仅为新版。
|
|
1181
|
+
*/
|
|
1182
|
+
function injectDotsViaDOM(modelByName, modelColor, userColor) {
|
|
1183
|
+
if (typeof document === "undefined" || typeof requestAnimationFrame === "undefined") return;
|
|
1184
|
+
const run = () => {
|
|
1185
|
+
const listbox = document.querySelector("[class*=\"_3e4SsG_menu\"]")?.querySelector("[role=\"listbox\"]") ?? document.querySelector("[role=\"listbox\"]");
|
|
1186
|
+
if (listbox === null) return;
|
|
1187
|
+
const options = listbox.querySelectorAll("[role=\"option\"]");
|
|
1188
|
+
for (const opt of options) {
|
|
1189
|
+
if (opt.querySelector("[data-skill-dot]") !== null) continue;
|
|
1190
|
+
const nameEl = opt.querySelector("[class*=\"itemName\"]");
|
|
1191
|
+
if (nameEl === null) continue;
|
|
1192
|
+
const name = (nameEl.textContent ?? "").trim();
|
|
1193
|
+
if (name.length === 0) continue;
|
|
1194
|
+
if (!modelByName.has(name)) continue;
|
|
1195
|
+
const color = modelByName.get(name) ?? true ? modelColor : userColor;
|
|
1196
|
+
const wrapper = document.createElement("span");
|
|
1197
|
+
wrapper.setAttribute("data-skill-dot", "");
|
|
1198
|
+
wrapper.setAttribute("aria-hidden", "true");
|
|
1199
|
+
wrapper.style.width = "16px";
|
|
1200
|
+
wrapper.style.height = "16px";
|
|
1201
|
+
wrapper.style.display = "inline-flex";
|
|
1202
|
+
wrapper.style.justifyContent = "center";
|
|
1203
|
+
wrapper.style.alignItems = "center";
|
|
1204
|
+
wrapper.style.flex = "none";
|
|
1205
|
+
const dot = document.createElement("span");
|
|
1206
|
+
dot.style.display = "inline-block";
|
|
1207
|
+
dot.style.width = "6px";
|
|
1208
|
+
dot.style.height = "6px";
|
|
1209
|
+
dot.style.borderRadius = "3px";
|
|
1210
|
+
dot.style.background = color;
|
|
1211
|
+
dot.style.flex = "none";
|
|
1212
|
+
wrapper.appendChild(dot);
|
|
1213
|
+
nameEl.parentElement?.insertBefore(wrapper, nameEl);
|
|
1214
|
+
}
|
|
1215
|
+
};
|
|
1216
|
+
requestAnimationFrame(() => setTimeout(run, 0));
|
|
1217
|
+
}
|
|
1218
|
+
function wrapSkillSource(source, api, scope) {
|
|
1219
|
+
const original = source.candidates;
|
|
1220
|
+
source.candidates = async (session, req) => {
|
|
1221
|
+
const items = await original(session, req);
|
|
1222
|
+
if (req.signal.aborted) return items;
|
|
1223
|
+
const isNewHost = req !== null && typeof req === "object" && "drilled" in req;
|
|
1224
|
+
const modelByName = await modelInvocableMap(api);
|
|
1225
|
+
if (req.signal.aborted) return items;
|
|
1226
|
+
const snapshot = scope.getSnapshot();
|
|
1227
|
+
const modelColor = snapshot.value?.dotModelColor ?? "#2f81f7";
|
|
1228
|
+
const userColor = snapshot.value?.dotUserColor ?? "#3fb950";
|
|
1229
|
+
if (isNewHost) {
|
|
1230
|
+
injectDotsViaDOM(modelByName, modelColor, userColor);
|
|
1231
|
+
return items;
|
|
1232
|
+
}
|
|
1233
|
+
return items.map((item) => ({
|
|
1234
|
+
...item,
|
|
1235
|
+
icon: dotIcon(modelByName.get(item.name) ?? true ? modelColor : userColor)
|
|
1236
|
+
}));
|
|
1237
|
+
};
|
|
1238
|
+
return () => {
|
|
1239
|
+
source.candidates = original;
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
/**
|
|
1243
|
+
* Mount the slash-menu dots on the registered `/skill` source. Idempotent and
|
|
1244
|
+
* defensive: if the core source isn't registered yet (or the registry shape
|
|
1245
|
+
* changes), it retries briefly and then gives up silently — the chat keeps
|
|
1246
|
+
* working, it simply shows no dots. The returned disposer restores the
|
|
1247
|
+
* original candidates and clears the model cache.
|
|
1248
|
+
* @param ctx - the client root context (inputTriggers + events).
|
|
1249
|
+
* @param api - hub browser API.
|
|
1250
|
+
* @param scope - hub settings scope for dot colors.
|
|
1251
|
+
* @returns a cleanup function for `ctx.effect`.
|
|
1252
|
+
*/
|
|
1253
|
+
function setupSkillSlashDots(ctx, api, scope) {
|
|
1254
|
+
const inputTriggers = ctx.get("inputTriggers");
|
|
1255
|
+
if (inputTriggers === void 0) return () => {};
|
|
1256
|
+
let disposed = false;
|
|
1257
|
+
let restore;
|
|
1258
|
+
let attempts = 0;
|
|
1259
|
+
const attempt = () => {
|
|
1260
|
+
if (disposed) return;
|
|
1261
|
+
const source = findSkillSource(inputTriggers);
|
|
1262
|
+
if (source === void 0) {
|
|
1263
|
+
if (attempts < 10) {
|
|
1264
|
+
attempts += 1;
|
|
1265
|
+
setTimeout(attempt, 100);
|
|
1266
|
+
}
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
restore = wrapSkillSource(source, api, scope);
|
|
1270
|
+
};
|
|
1271
|
+
attempt();
|
|
1272
|
+
const clearCache = () => {
|
|
1273
|
+
resetModelCache();
|
|
1274
|
+
};
|
|
1275
|
+
const offReset = ctx.on("connection/reset", clearCache);
|
|
1276
|
+
return () => {
|
|
1277
|
+
disposed = true;
|
|
1278
|
+
offReset();
|
|
1279
|
+
restore?.();
|
|
1280
|
+
restore = void 0;
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
//#endregion
|
|
849
1284
|
//#region src/client/icons.tsx
|
|
850
1285
|
/** ic_ds_chevron_down_outline_14 */
|
|
851
1286
|
function IconChevronDownOutline14({ size = 14, className }) {
|
|
@@ -2078,7 +2513,7 @@ window.__ModuleLoader__.load({
|
|
|
2078
2513
|
}
|
|
2079
2514
|
async store(field, value) {
|
|
2080
2515
|
await this.scope.set(field, value);
|
|
2081
|
-
return this.
|
|
2516
|
+
return this.userRecord()?.[field] === value;
|
|
2082
2517
|
}
|
|
2083
2518
|
userRecord() {
|
|
2084
2519
|
const user = this.scope.getSnapshot().user;
|
|
@@ -2101,9 +2536,6 @@ window.__ModuleLoader__.load({
|
|
|
2101
2536
|
const base = this.scope.getSnapshot().base;
|
|
2102
2537
|
return typeof base === "object" && base !== null ? base[field] : void 0;
|
|
2103
2538
|
}
|
|
2104
|
-
userLayer() {
|
|
2105
|
-
return this.userRecord();
|
|
2106
|
-
}
|
|
2107
2539
|
stored(field) {
|
|
2108
2540
|
const user = this.userRecord();
|
|
2109
2541
|
return user !== void 0 && Object.hasOwn(user, field);
|
|
@@ -2113,141 +2545,6 @@ window.__ModuleLoader__.load({
|
|
|
2113
2545
|
}
|
|
2114
2546
|
};
|
|
2115
2547
|
//#endregion
|
|
2116
|
-
//#region src/client/grouping.ts
|
|
2117
|
-
/**
|
|
2118
|
-
* Derive a group switch view from its member names and the set of currently
|
|
2119
|
-
* enabled skill names (catalog.skills). Members not in the enabled set count
|
|
2120
|
-
* as disabled (catalog.disabled or absent read-only rows).
|
|
2121
|
-
*/
|
|
2122
|
-
function groupSwitchView(members, enabledNames) {
|
|
2123
|
-
const enabled = [];
|
|
2124
|
-
const disabled = [];
|
|
2125
|
-
for (const name of members) if (enabledNames.has(name)) enabled.push(name);
|
|
2126
|
-
else disabled.push(name);
|
|
2127
|
-
return {
|
|
2128
|
-
state: disabled.length === 0 ? "on" : enabled.length === 0 ? "off" : "mixed",
|
|
2129
|
-
enabled,
|
|
2130
|
-
disabled
|
|
2131
|
-
};
|
|
2132
|
-
}
|
|
2133
|
-
/** Names of every group a skill belongs to (tags + collections). */
|
|
2134
|
-
function groupNamesOf(name, tags, collections) {
|
|
2135
|
-
const names = [];
|
|
2136
|
-
for (const tag of tags) if (tag.skillNames.includes(name)) names.push(tag.name);
|
|
2137
|
-
for (const collection of collections) if (collection.skillNames.includes(name)) names.push(collection.name);
|
|
2138
|
-
return names;
|
|
2139
|
-
}
|
|
2140
|
-
/**
|
|
2141
|
-
* Members of a group that are currently enabled AND also belong to at least
|
|
2142
|
-
* one other group — the set a "close" action must ask about.
|
|
2143
|
-
*/
|
|
2144
|
-
function conflictsOnClose(members, enabledNames, otherGroups) {
|
|
2145
|
-
return members.filter((name) => {
|
|
2146
|
-
if (!enabledNames.has(name)) return false;
|
|
2147
|
-
return otherGroups.some((group) => group.members.includes(name));
|
|
2148
|
-
});
|
|
2149
|
-
}
|
|
2150
|
-
/** Origin-repo filter value: skills with no source record (private skills). */
|
|
2151
|
-
const PRIVATE_SOURCE = "private";
|
|
2152
|
-
/**
|
|
2153
|
-
* Apply the origin filter ('all' or a specific origin repo; skills without a
|
|
2154
|
-
* source record count as PRIVATE_SOURCE). The origins map is the store's
|
|
2155
|
-
* skillName → repo derivation, so filtering follows the tracked source
|
|
2156
|
-
* records instead of the filesystem root a skill happens to live under.
|
|
2157
|
-
* 项目级技能(有 workspace 归属)永远不算「个人」。
|
|
2158
|
-
*/
|
|
2159
|
-
function filterBySource(skills, source, origins) {
|
|
2160
|
-
if (source === "all") return [...skills];
|
|
2161
|
-
return skills.filter((skill) => {
|
|
2162
|
-
if (isProjectSource(skill.source)) return false;
|
|
2163
|
-
return (origins[skill.name] ?? "private") === source;
|
|
2164
|
-
});
|
|
2165
|
-
}
|
|
2166
|
-
/**
|
|
2167
|
-
* Sort a skill list in place-safe copy order: name ascending, added
|
|
2168
|
-
* descending (newest first, unknown addedAt last), or uses descending
|
|
2169
|
-
* (most-called first). Unknown values always trail.
|
|
2170
|
-
*/
|
|
2171
|
-
function sortSkills(skills, key, getUses) {
|
|
2172
|
-
const list = [...skills];
|
|
2173
|
-
if (key === "name") list.sort((a, b) => a.name.localeCompare(b.name));
|
|
2174
|
-
else if (key === "added") list.sort((a, b) => (b.addedAt ?? -Infinity) - (a.addedAt ?? -Infinity));
|
|
2175
|
-
else if (key === "uses") list.sort((a, b) => (getUses?.(b.name) ?? 0) - (getUses?.(a.name) ?? 0));
|
|
2176
|
-
return list;
|
|
2177
|
-
}
|
|
2178
|
-
/** Format an epoch-ms timestamp as a short relative time bucket. */
|
|
2179
|
-
function formatRelativeTime(ms, now = Date.now()) {
|
|
2180
|
-
const diff = Math.max(0, now - ms);
|
|
2181
|
-
const minutes = Math.floor(diff / 6e4);
|
|
2182
|
-
if (minutes < 1) return { key: "time.justNow" };
|
|
2183
|
-
if (minutes < 60) return {
|
|
2184
|
-
key: "time.minutesAgo",
|
|
2185
|
-
value: minutes
|
|
2186
|
-
};
|
|
2187
|
-
const hours = Math.floor(minutes / 60);
|
|
2188
|
-
if (hours < 24) return {
|
|
2189
|
-
key: "time.hoursAgo",
|
|
2190
|
-
value: hours
|
|
2191
|
-
};
|
|
2192
|
-
const days = Math.floor(hours / 24);
|
|
2193
|
-
if (days < 7) return {
|
|
2194
|
-
key: "time.daysAgo",
|
|
2195
|
-
value: days
|
|
2196
|
-
};
|
|
2197
|
-
return {
|
|
2198
|
-
key: "time.weeksAgo",
|
|
2199
|
-
value: Math.floor(days / 7)
|
|
2200
|
-
};
|
|
2201
|
-
}
|
|
2202
|
-
//#endregion
|
|
2203
|
-
//#region src/client/helpers.ts
|
|
2204
|
-
/**
|
|
2205
|
-
* Shared panel helpers: the active-dictionary pick (document-language
|
|
2206
|
-
* based, family precedent) plus a small error-message extractor.
|
|
2207
|
-
*/
|
|
2208
|
-
/** Active dictionary, picked by the document language at call time. */
|
|
2209
|
-
function dictionary() {
|
|
2210
|
-
return (typeof document !== "undefined" ? document.documentElement.lang : "zh").toLowerCase().startsWith("en") ? { ...en } : { ...zh };
|
|
2211
|
-
}
|
|
2212
|
-
/** Translate a key with optional {name} template params (current language). */
|
|
2213
|
-
function tt(key, values) {
|
|
2214
|
-
return t(dictionary(), key, values);
|
|
2215
|
-
}
|
|
2216
|
-
/** Human-readable error text from an unknown thrown value. */
|
|
2217
|
-
function errorMessage(error) {
|
|
2218
|
-
if (error instanceof Error) return error.message;
|
|
2219
|
-
return String(error);
|
|
2220
|
-
}
|
|
2221
|
-
//#endregion
|
|
2222
|
-
//#region src/client/panel/format.ts
|
|
2223
|
-
/** Model-invocable dot color default. Single source for the TS side; the
|
|
2224
|
-
* panel's CSS mirrors it via --hub-model (panel.module.css). */
|
|
2225
|
-
const DEFAULT_DOT_MODEL_COLOR = "#2f81f7";
|
|
2226
|
-
/** User-invocable dot color default. Single source for the TS side; the
|
|
2227
|
-
* panel's CSS mirrors it via --hub-user (panel.module.css). */
|
|
2228
|
-
const DEFAULT_DOT_USER_COLOR = "#3fb950";
|
|
2229
|
-
/** Dot inline style from the user-chosen color (undefined keeps the CSS default). */
|
|
2230
|
-
function dotStyle(color) {
|
|
2231
|
-
if (color === void 0) return void 0;
|
|
2232
|
-
return {
|
|
2233
|
-
background: color,
|
|
2234
|
-
borderColor: color
|
|
2235
|
-
};
|
|
2236
|
-
}
|
|
2237
|
-
/** Localized relative-time text for a Unix-ms timestamp. */
|
|
2238
|
-
function relativeTimeText(ms) {
|
|
2239
|
-
const rt = formatRelativeTime(ms);
|
|
2240
|
-
return tt(rt.key, rt.value !== void 0 ? { value: rt.value } : void 0);
|
|
2241
|
-
}
|
|
2242
|
-
/** Localized absolute time for the detail meta (e.g. "2026/8/16 11:00:00"). */
|
|
2243
|
-
function formatDateTime(ms) {
|
|
2244
|
-
return new Date(ms).toLocaleString();
|
|
2245
|
-
}
|
|
2246
|
-
/** Short form of a commit SHA for display. */
|
|
2247
|
-
function shortSha(sha) {
|
|
2248
|
-
return sha.length > 7 ? sha.slice(0, 7) : sha;
|
|
2249
|
-
}
|
|
2250
|
-
//#endregion
|
|
2251
2548
|
//#region src/client/SkillHubSettingsCard.tsx
|
|
2252
2549
|
/**
|
|
2253
2550
|
* The dsh-skill-hub plugin settings card: bridges the hub's settings
|
|
@@ -2466,7 +2763,7 @@ window.__ModuleLoader__.load({
|
|
|
2466
2763
|
}
|
|
2467
2764
|
//#endregion
|
|
2468
2765
|
//#region \0dsh-css:/Users/huanyi/Documents/personal/tools/dsh-skill-hub/src/client/panel/panel.module.css.mjs
|
|
2469
|
-
const css = "._6VtqdG_panel{-webkit-font-smoothing:antialiased;color-scheme:light dark;--hub-model:#2f81f7;--hub-user:#3fb950;flex-direction:column;gap:14px;max-width:720px;margin:0 auto;padding:28px 20px 44px;font-family:-apple-system,BlinkMacSystemFont,SF Pro Text,Helvetica Neue,sans-serif;display:flex}._6VtqdG_header{flex-wrap:wrap;align-items:baseline;gap:10px;display:flex}._6VtqdG_title{letter-spacing:-.02em;margin:0;font-size:24px;font-weight:700}._6VtqdG_hint{opacity:.5;font-size:12px}._6VtqdG_actions{gap:8px;margin-left:auto;display:flex}._6VtqdG_segmented{background:#80808024;border-radius:9px;gap:2px;padding:2px;display:flex}._6VtqdG_segBtn{color:inherit;opacity:.62;cursor:pointer;background:0 0;border:none;border-radius:7px;padding:5px 13px;font-size:12px;transition:background .15s,opacity .15s}._6VtqdG_segBtn:hover{opacity:.9}._6VtqdG_segBtnActive{opacity:1;background:#80808047;font-weight:600}._6VtqdG_search{width:100%;color:inherit;background:#8080801f;border:none;border-radius:10px;outline:none;padding:10px 14px;font-size:13px;transition:background .15s}._6VtqdG_search::placeholder{opacity:.45}._6VtqdG_search:focus{background:#8080802e}._6VtqdG_section{background:#8080800f;border-radius:12px;flex-direction:column;margin-top:4px;display:flex;overflow:hidden;box-shadow:0 1px 2px #00000008,0 4px 12px #0000000a}._6VtqdG_sectionTitle{margin:14px 16px 2px;font-size:13px;font-weight:700}._6VtqdG_groupHead{box-sizing:border-box;align-items:center;gap:8px;min-height:44px;padding:10px 14px;display:flex}._6VtqdG_groupTitle{opacity:.92;flex:1;align-items:center;gap:6px;min-width:0;font-size:13px;font-weight:600;line-height:1;display:inline-flex}._6VtqdG_groupOps{flex-wrap:wrap;flex:none;align-items:center;gap:6px;display:inline-flex}._6VtqdG_opBtn{color:inherit;cursor:pointer;background:#80808021;border:none;border-radius:999px;padding:3px 11px;font-size:11px;transition:background .15s}._6VtqdG_opBtn:hover{background:#80808038}._6VtqdG_opBtn:disabled{opacity:.4;cursor:default}._6VtqdG_row{cursor:pointer;background:0 0;align-items:center;gap:12px;padding:10px 14px;transition:background .12s;display:flex}._6VtqdG_row:hover{background:#80808014}._6VtqdG_row:focus-visible{outline:2px solid var(--hub-model);outline-offset:-2px}._6VtqdG_rowStatic{cursor:default}._6VtqdG_rowStatic:hover{background:0 0}._6VtqdG_row+._6VtqdG_row{border-top:.5px solid #80808021}._6VtqdG_rowMain{flex:1;min-width:0}._6VtqdG_rowName{align-items:center;gap:5px;font-size:13.5px;display:flex}._6VtqdG_rowNameText{font-weight:600}._6VtqdG_rowDesc{opacity:.52;white-space:nowrap;text-overflow:ellipsis;margin-top:1px;font-size:12.5px;overflow:hidden}._6VtqdG_rowMeta{opacity:.45;white-space:nowrap;flex:none;font-size:11.5px}._6VtqdG_badges{flex:none;align-items:center;gap:6px;display:flex}._6VtqdG_badge{opacity:.8;white-space:nowrap;border:.5px solid #8080804d;border-radius:999px;padding:1px 7px;font-size:10.5px}._6VtqdG_badgeModel{color:var(--hub-model);border-color:currentColor}._6VtqdG_badgeUser{color:var(--hub-user);border-color:currentColor}._6VtqdG_badgeUses{color:#d29922;border-color:currentColor}._6VtqdG_badgeReadonly{opacity:.5}._6VtqdG_switch{cursor:pointer;background:#8080804d;border:none;border-radius:999px;flex:none;width:36px;height:21px;padding:2px;transition:background .18s}._6VtqdG_switch:disabled{opacity:.5;cursor:default}._6VtqdG_switchOn{background:#34c759}._6VtqdG_switchThumb{background:#fff;border-radius:50%;width:17px;height:17px;transition:transform .18s;display:block;transform:translate(0);box-shadow:0 1px 2px #00000040}._6VtqdG_switchOn ._6VtqdG_switchThumb{transform:translate(15px)}._6VtqdG_empty{opacity:.5;text-align:center;padding:20px 0;font-size:13px}._6VtqdG_diagRow{background:#d299220f;border-left:3px solid #d29922;padding:9px 14px;font-size:12px}._6VtqdG_diagPath{opacity:.75;word-break:break-all;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}._6VtqdG_diagReason{opacity:.65;margin-top:1px}._6VtqdG_updateLink{color:inherit;font-weight:600;text-decoration:underline}._6VtqdG_errorBanner{color:#d1242f;white-space:pre-wrap;word-break:break-all;background:#d1242f12;border-radius:10px;align-items:flex-start;gap:10px;padding:10px 14px;font-size:12.5px;display:flex}._6VtqdG_successBanner{color:#3fb950;white-space:pre-wrap;word-break:break-all;background:#3fb95014;border-radius:10px;align-items:flex-start;gap:10px;padding:10px 14px;font-size:12.5px;display:flex}._6VtqdG_detailHead{flex-wrap:wrap;align-items:center;gap:10px;display:flex}._6VtqdG_back{color:inherit;cursor:pointer;background:#8080801f;border:none;border-radius:8px;padding:6px 12px;font-size:12.5px}._6VtqdG_back:hover{background:#80808033}._6VtqdG_detailName{letter-spacing:-.01em;font-size:19px;font-weight:700}._6VtqdG_detailMeta{opacity:.6;flex-direction:column;gap:4px;font-size:12px;display:flex}._6VtqdG_detailMetaLine{word-break:break-all}._6VtqdG_detailContent{white-space:pre-wrap;word-break:break-word;background:#8080800d;border-radius:12px;max-height:62vh;padding:14px 16px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.6;overflow:auto}._6VtqdG_form{background:#8080800d;border-radius:12px;flex-direction:column;gap:10px;padding:14px 16px;display:flex;box-shadow:0 1px 2px #00000008,0 4px 12px #0000000a}._6VtqdG_formRow{flex-direction:column;gap:5px;display:flex}._6VtqdG_formLabel{opacity:.6;font-size:12px}._6VtqdG_input,._6VtqdG_select{color:inherit;background:#8080801f;border:none;border-radius:9px;outline:none;padding:9px 12px;font-size:13px}._6VtqdG_input:focus,._6VtqdG_select:focus{background:#8080802e}._6VtqdG_buttons{align-items:center;gap:8px;display:flex}._6VtqdG_button{cursor:pointer;color:inherit;background:#8080801f;border:none;border-radius:8px;padding:6px 13px;font-size:12.5px;transition:background .15s}._6VtqdG_button:hover{background:#80808033}._6VtqdG_primary{background:var(--hub-model);color:#fff}._6VtqdG_primary:hover{opacity:.92;background:#2f81f7}._6VtqdG_primary:disabled{opacity:.45;cursor:default}._6VtqdG_danger{color:#fff;background:#d1242f}._6VtqdG_danger:hover{opacity:.92;background:#d1242f}._6VtqdG_danger:disabled{opacity:.45;cursor:default}._6VtqdG_formError{color:#d1242f}._6VtqdG_formSuccess{color:#3fb950}._6VtqdG_muted{opacity:.55}._6VtqdG_dot{vertical-align:middle;border-radius:50%;width:7px;height:7px;margin-left:5px;display:inline-block}._6VtqdG_dotModel{background:var(--hub-model)}._6VtqdG_dotUser{background:var(--hub-user)}._6VtqdG_legend{opacity:.55;flex-wrap:wrap;align-items:center;gap:12px;padding:2px 2px 0;font-size:11.5px;display:flex}._6VtqdG_legendItem{align-items:center;gap:4px;display:inline-flex}._6VtqdG_legendItem ._6VtqdG_dot{margin-left:0}._6VtqdG_legendHint{opacity:.8}._6VtqdG_badgeSource,._6VtqdG_badgeCount{opacity:.72;background:#8080801a;border:none}._6VtqdG_badgeDisabled{opacity:.58;background:#8080801a;border:none}._6VtqdG_disclosure{min-width:0;color:inherit;cursor:pointer;text-align:left;background:0 0;border:none;flex:1;align-items:center;gap:7px;min-height:24px;padding:2px 0;display:flex}._6VtqdG_disclosure:hover ._6VtqdG_groupTitle{opacity:1}._6VtqdG_chevron{opacity:.55;border-bottom:1.5px solid;border-right:1.5px solid;flex:none;align-self:center;width:8px;height:8px;margin-right:2px;transition:transform .15s;transform:rotate(45deg)translateY(-1px)}._6VtqdG_chevronCollapsed{transform:rotate(-45deg)translateY(-1px)}._6VtqdG_headerCount{opacity:.5;white-space:nowrap;margin-left:2px;font-size:12px}._6VtqdG_pluginVersion{opacity:.45;white-space:nowrap;font-variant-numeric:tabular-nums;margin-left:6px;font-size:12px;font-weight:500}._6VtqdG_subbar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_legendToggle{width:22px;height:22px;color:inherit;cursor:pointer;opacity:.55;background:#80808024;border:none;border-radius:50%;flex:none;font-size:12px;line-height:1;transition:opacity .15s,background .15s}._6VtqdG_legendToggle:hover{opacity:1}._6VtqdG_legendToggleActive{opacity:1;background:#80808047}._6VtqdG_filterBar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_filterBar ._6VtqdG_formLabel{margin:0}._6VtqdG_useCount{color:#d29922;vertical-align:1px;background:#d299221f;border-radius:999px;margin-left:6px;padding:0 6px;font-size:11px;font-weight:700;line-height:1.6;display:inline-block}._6VtqdG_useTime{opacity:.45;white-space:nowrap;margin-left:auto;font-size:11px}._6VtqdG_switchMixed{background:#2f81f773}._6VtqdG_switchMixed ._6VtqdG_switchThumb{transform:translate(7.5px);box-shadow:0 1px 2px #00000040}._6VtqdG_groupTitleInner{align-items:baseline;display:inline-flex}._6VtqdG_sourceLink{color:inherit;opacity:.9;border-bottom:1px dotted;font-weight:600;text-decoration:none}._6VtqdG_sourceLink:hover{opacity:1}._6VtqdG_statusBadges{flex:none;align-items:center;gap:4px;display:inline-flex}._6VtqdG_statusBadge{opacity:.8;white-space:nowrap;background:#8080801a;border:.5px solid #8080804d;border-radius:999px;padding:1px 7px;font-size:10.5px}._6VtqdG_statusOk{color:#3fb950;background:0 0;border-color:currentColor}._6VtqdG_statusUpdated{color:#d29922;background:#d2992214;border-color:currentColor}._6VtqdG_statusError{color:#d1242f;background:#d1242f14;border-color:currentColor}._6VtqdG_statusWrap{cursor:pointer;background:0 0;border:none;align-items:center;gap:4px;padding:0;font-family:inherit;display:inline-flex}._6VtqdG_statusWrap:hover ._6VtqdG_statusBadge{opacity:1}._6VtqdG_statusButton{cursor:pointer;font-family:inherit}._6VtqdG_statusButton:hover{opacity:1}._6VtqdG_opDanger{color:#d1242f;background:#d1242f1a}._6VtqdG_opDanger:hover{background:#d1242f2e}._6VtqdG_iconBtn{border-radius:999px;justify-content:center;align-items:center;width:24px;height:24px;padding:0;display:inline-flex}._6VtqdG_sourceCard{background:#8080800d;border-radius:12px;flex-direction:column;gap:4px;padding:12px 14px;font-size:12px;display:flex}._6VtqdG_sourceCardTitle{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_dialogOverlay{z-index:50;-webkit-backdrop-filter:blur(2px);background:#00000059;justify-content:center;align-items:center;padding:24px;display:flex;position:fixed;inset:0}._6VtqdG_dialog{background:var(--dsw-surface,#f5f5f7);min-width:min(320px,100%);max-width:380px;color:var(--dsw-text,#1d1d1f);border-radius:14px;padding:18px 18px 14px;box-shadow:0 12px 40px #00000047,0 2px 8px #00000029}@media (prefers-color-scheme:dark){._6VtqdG_dialog{color:#f5f5f7;background:#2c2c2e}}._6VtqdG_dialogTitle{letter-spacing:-.01em;margin:0 0 8px;font-size:15px;font-weight:700}._6VtqdG_dialogText{opacity:.65;margin:0 0 10px;font-size:12.5px;line-height:1.5}._6VtqdG_dialogList{flex-direction:column;gap:5px;max-height:200px;margin:0 0 14px;padding:0;list-style:none;display:flex;overflow:auto}._6VtqdG_dialogList li{opacity:.85;word-break:break-all;font-size:12.5px}._6VtqdG_dialogActions{justify-content:flex-end;gap:8px;display:flex}._6VtqdG_filterBar ._6VtqdG_search{flex:1;min-width:160px}._6VtqdG_filterBar ._6VtqdG_formLabel{flex:none}._6VtqdG_filterBar ._6VtqdG_select{flex:none;max-width:150px}._6VtqdG_filterBar ._6VtqdG_segmented{flex:none}._6VtqdG_titleIcon{vertical-align:-2px;opacity:.8;margin-right:2px;display:inline-block}._6VtqdG_grow{flex:1}._6VtqdG_hintLine{opacity:.55;margin:0;font-size:11.5px}._6VtqdG_hintPadded{margin:6px 12px 2px}._6VtqdG_hintInline{margin:4px 2px}._6VtqdG_rowMuted{cursor:default;opacity:.55}._6VtqdG_actionsPadded{padding:8px 12px}._6VtqdG_actionsTop{margin-top:8px}._6VtqdG_actionsBottom{margin-bottom:8px}._6VtqdG_groupTime{margin-left:6px}._6VtqdG_sectionHeadRow{align-items:center;gap:8px;display:flex}._6VtqdG_sectionTitleFill{flex:1}._6VtqdG_dialogSelect{width:100%;margin-bottom:12px}._6VtqdG_dialogRow{align-items:center;gap:8px;display:flex}._6VtqdG_workspaceBox{align-items:center;gap:6px;display:inline-flex}._6VtqdG_workspaceInput{width:200px;padding:6px 10px;font-size:12px}._6VtqdG_projectNest{border-left:1px solid #80808024;flex-direction:column;margin:2px 0 0 14px;display:flex}._6VtqdG_dragHandle{cursor:grab;opacity:.32;letter-spacing:1px;user-select:none;flex:none;justify-content:center;align-self:center;align-items:center;height:20px;padding:0 4px;font-size:11px;display:inline-flex}._6VtqdG_dragHandle:active{cursor:grabbing}._6VtqdG_dragging{opacity:.45}._6VtqdG_dragOver{outline:2px dashed var(--hub-model,#2f81f7);outline-offset:-2px;background:#2f81f712}._6VtqdG_scanList{background:#8080800f;border:.5px solid #80808021;border-radius:12px;max-height:360px;margin-top:8px;overflow:auto}._6VtqdG_scanProgressTrack{background:#80808021;border-radius:4px;flex:1;height:6px;overflow:hidden}._6VtqdG_scanProgressFill{background:var(--hub-model,#2f81f7);height:100%;transition:width .2s}";
|
|
2766
|
+
const css = "._6VtqdG_panel{-webkit-font-smoothing:antialiased;color-scheme:light dark;--hub-model:#2f81f7;--hub-user:#3fb950;flex-direction:column;gap:14px;max-width:720px;margin:0 auto;padding:28px 20px 44px;font-family:-apple-system,BlinkMacSystemFont,SF Pro Text,Helvetica Neue,sans-serif;display:flex}._6VtqdG_header{flex-wrap:wrap;align-items:baseline;gap:10px;display:flex}._6VtqdG_title{letter-spacing:-.02em;margin:0;font-size:24px;font-weight:700}._6VtqdG_hint{opacity:.5;font-size:12px}._6VtqdG_actions{gap:8px;margin-left:auto;display:flex}._6VtqdG_segmented{background:#80808024;border-radius:9px;gap:2px;padding:2px;display:flex}._6VtqdG_segBtn{color:inherit;opacity:.62;cursor:pointer;background:0 0;border:none;border-radius:7px;padding:5px 13px;font-size:12px;transition:background .15s,opacity .15s}._6VtqdG_segBtn:hover{opacity:.9}._6VtqdG_segBtnActive{opacity:1;background:#80808047;font-weight:600}._6VtqdG_search{width:100%;color:inherit;background:#8080801f;border:none;border-radius:10px;outline:none;padding:10px 14px;font-size:13px;transition:background .15s}._6VtqdG_search::placeholder{opacity:.45}._6VtqdG_search:focus{background:#8080802e}._6VtqdG_section{background:#8080800f;border-radius:12px;flex-direction:column;margin-top:4px;display:flex;overflow:hidden;box-shadow:0 1px 2px #00000008,0 4px 12px #0000000a}._6VtqdG_sectionTitle{margin:14px 16px 2px;font-size:13px;font-weight:700}._6VtqdG_groupHead{box-sizing:border-box;align-items:center;gap:8px;min-height:44px;padding:10px 14px;display:flex}._6VtqdG_groupTitle{opacity:.92;flex:1;align-items:center;gap:6px;min-width:0;font-size:13px;font-weight:600;line-height:1;display:inline-flex}._6VtqdG_groupOps{flex-wrap:wrap;flex:none;align-items:center;gap:6px;display:inline-flex}._6VtqdG_opBtn{color:inherit;cursor:pointer;background:#80808021;border:none;border-radius:999px;padding:3px 11px;font-size:11px;transition:background .15s}._6VtqdG_opBtn:hover{background:#80808038}._6VtqdG_opBtn:disabled{opacity:.4;cursor:default}._6VtqdG_row{cursor:pointer;background:0 0;align-items:center;gap:12px;padding:10px 14px;transition:background .12s;display:flex}._6VtqdG_row:hover{background:#80808014}._6VtqdG_row:focus-visible{outline:2px solid var(--hub-model);outline-offset:-2px}._6VtqdG_rowStatic{cursor:default}._6VtqdG_rowStatic:hover{background:0 0}._6VtqdG_row+._6VtqdG_row{border-top:.5px solid #80808021}._6VtqdG_rowMain{flex:1;min-width:0}._6VtqdG_rowName{align-items:center;gap:5px;font-size:13.5px;display:flex}._6VtqdG_rowNameText{font-weight:600}._6VtqdG_rowDesc{opacity:.52;white-space:nowrap;text-overflow:ellipsis;margin-top:1px;font-size:12.5px;overflow:hidden}._6VtqdG_badges{flex:none;align-items:center;gap:6px;display:flex}._6VtqdG_badge{opacity:.8;white-space:nowrap;border:.5px solid #8080804d;border-radius:999px;padding:1px 7px;font-size:10.5px}._6VtqdG_badgeReadonly{opacity:.5}._6VtqdG_switch{cursor:pointer;background:#8080804d;border:none;border-radius:999px;flex:none;width:36px;height:21px;padding:2px;transition:background .18s}._6VtqdG_switch:disabled{opacity:.5;cursor:default}._6VtqdG_switchOn{background:#34c759}._6VtqdG_switchThumb{background:#fff;border-radius:50%;width:17px;height:17px;transition:transform .18s;display:block;transform:translate(0);box-shadow:0 1px 2px #00000040}._6VtqdG_switchOn ._6VtqdG_switchThumb{transform:translate(15px)}._6VtqdG_empty{opacity:.5;text-align:center;padding:20px 0;font-size:13px}._6VtqdG_diagRow{background:#d299220f;border-left:3px solid #d29922;padding:9px 14px;font-size:12px}._6VtqdG_diagPath{opacity:.75;word-break:break-all;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}._6VtqdG_diagReason{opacity:.65;margin-top:1px}._6VtqdG_updateLink{color:inherit;font-weight:600;text-decoration:underline}._6VtqdG_errorBanner{color:#d1242f;white-space:pre-wrap;word-break:break-all;background:#d1242f12;border-radius:10px;align-items:flex-start;gap:10px;padding:10px 14px;font-size:12.5px;display:flex}._6VtqdG_successBanner{color:#3fb950;white-space:pre-wrap;word-break:break-all;background:#3fb95014;border-radius:10px;align-items:flex-start;gap:10px;padding:10px 14px;font-size:12.5px;display:flex}._6VtqdG_detailHead{flex-wrap:wrap;align-items:center;gap:10px;display:flex}._6VtqdG_back{color:inherit;cursor:pointer;background:#8080801f;border:none;border-radius:8px;padding:6px 12px;font-size:12.5px}._6VtqdG_back:hover{background:#80808033}._6VtqdG_detailName{letter-spacing:-.01em;font-size:19px;font-weight:700}._6VtqdG_detailMeta{opacity:.6;flex-direction:column;gap:4px;font-size:12px;display:flex}._6VtqdG_detailMetaLine{word-break:break-all}._6VtqdG_detailContent{white-space:pre-wrap;word-break:break-word;background:#8080800d;border-radius:12px;max-height:62vh;padding:14px 16px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.6;overflow:auto}._6VtqdG_form{background:#8080800d;border-radius:12px;flex-direction:column;gap:10px;padding:14px 16px;display:flex;box-shadow:0 1px 2px #00000008,0 4px 12px #0000000a}._6VtqdG_formRow{flex-direction:column;gap:5px;display:flex}._6VtqdG_formLabel{opacity:.6;font-size:12px}._6VtqdG_input,._6VtqdG_select{color:inherit;background:#8080801f;border:none;border-radius:9px;outline:none;padding:9px 12px;font-size:13px}._6VtqdG_input:focus,._6VtqdG_select:focus{background:#8080802e}._6VtqdG_buttons{align-items:center;gap:8px;display:flex}._6VtqdG_button{cursor:pointer;color:inherit;background:#8080801f;border:none;border-radius:8px;padding:6px 13px;font-size:12.5px;transition:background .15s}._6VtqdG_button:hover{background:#80808033}._6VtqdG_primary{background:var(--hub-model);color:#fff}._6VtqdG_primary:hover{opacity:.92;background:#2f81f7}._6VtqdG_primary:disabled{opacity:.45;cursor:default}._6VtqdG_danger{color:#fff;background:#d1242f}._6VtqdG_danger:hover{opacity:.92;background:#d1242f}._6VtqdG_danger:disabled{opacity:.45;cursor:default}._6VtqdG_formError{color:#d1242f}._6VtqdG_formSuccess{color:#3fb950}._6VtqdG_muted{opacity:.55}._6VtqdG_dot{vertical-align:middle;border-radius:50%;width:7px;height:7px;margin-left:5px;display:inline-block}._6VtqdG_dotModel{background:var(--hub-model)}._6VtqdG_dotUser{background:var(--hub-user)}._6VtqdG_displayName{opacity:.55;white-space:nowrap;text-overflow:ellipsis;flex-shrink:1;min-width:0;max-width:160px;margin-left:6px;font-size:11px;font-weight:400;overflow:hidden}._6VtqdG_legend{opacity:.55;flex-wrap:wrap;align-items:center;gap:12px;padding:2px 2px 0;font-size:11.5px;display:flex}._6VtqdG_legendItem{align-items:center;gap:4px;display:inline-flex}._6VtqdG_legendItem ._6VtqdG_dot{margin-left:0}._6VtqdG_legendHint{opacity:.8}._6VtqdG_badgeSource,._6VtqdG_badgeCount{opacity:.72;background:#8080801a;border:none}._6VtqdG_disclosure{min-width:0;color:inherit;cursor:pointer;text-align:left;background:0 0;border:none;flex:1;align-items:center;gap:7px;min-height:24px;padding:2px 0;display:flex}._6VtqdG_disclosure:hover ._6VtqdG_groupTitle{opacity:1}._6VtqdG_chevron{opacity:.55;border-bottom:1.5px solid;border-right:1.5px solid;flex:none;align-self:center;width:8px;height:8px;margin-right:2px;transition:transform .15s;transform:rotate(45deg)translateY(-1px)}._6VtqdG_chevronCollapsed{transform:rotate(-45deg)translateY(-1px)}._6VtqdG_headerCount{opacity:.5;white-space:nowrap;margin-left:2px;font-size:12px}._6VtqdG_pluginVersion{opacity:.45;white-space:nowrap;font-variant-numeric:tabular-nums;margin-left:6px;font-size:12px;font-weight:500}._6VtqdG_subbar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_legendToggle{width:22px;height:22px;color:inherit;cursor:pointer;opacity:.55;background:#80808024;border:none;border-radius:50%;flex:none;font-size:12px;line-height:1;transition:opacity .15s,background .15s}._6VtqdG_legendToggle:hover{opacity:1}._6VtqdG_legendToggleActive{opacity:1;background:#80808047}._6VtqdG_filterBar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_filterBar ._6VtqdG_formLabel{margin:0}._6VtqdG_useCount{color:#d29922;vertical-align:1px;background:#d299221f;border-radius:999px;margin-left:6px;padding:0 6px;font-size:11px;font-weight:700;line-height:1.6;display:inline-block}._6VtqdG_useTime{opacity:.45;white-space:nowrap;margin-left:auto;font-size:11px}._6VtqdG_switchMixed{background:#2f81f773}._6VtqdG_switchMixed ._6VtqdG_switchThumb{transform:translate(7.5px);box-shadow:0 1px 2px #00000040}._6VtqdG_groupTitleInner{align-items:baseline;display:inline-flex}._6VtqdG_sourceLink{color:inherit;opacity:.9;border-bottom:1px dotted;font-weight:600;text-decoration:none}._6VtqdG_sourceLink:hover{opacity:1}._6VtqdG_statusBadges{flex:none;align-items:center;gap:4px;display:inline-flex}._6VtqdG_statusBadge{opacity:.8;white-space:nowrap;background:#8080801a;border:.5px solid #8080804d;border-radius:999px;padding:1px 7px;font-size:10.5px}._6VtqdG_statusOk{color:#3fb950;background:0 0;border-color:currentColor}._6VtqdG_statusUpdated{color:#d29922;background:#d2992214;border-color:currentColor}._6VtqdG_statusError{color:#d1242f;background:#d1242f14;border-color:currentColor}._6VtqdG_statusWrap{cursor:pointer;background:0 0;border:none;align-items:center;gap:4px;padding:0;font-family:inherit;display:inline-flex}._6VtqdG_statusWrap:hover ._6VtqdG_statusBadge{opacity:1}._6VtqdG_statusButton{cursor:pointer;font-family:inherit}._6VtqdG_statusButton:hover{opacity:1}._6VtqdG_opDanger{color:#d1242f;background:#d1242f1a}._6VtqdG_opDanger:hover{background:#d1242f2e}._6VtqdG_iconBtn{border-radius:999px;justify-content:center;align-items:center;width:24px;height:24px;padding:0;display:inline-flex}._6VtqdG_sourceCard{background:#8080800d;border-radius:12px;flex-direction:column;gap:4px;padding:12px 14px;font-size:12px;display:flex}._6VtqdG_sourceCardTitle{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_dialogOverlay{z-index:50;-webkit-backdrop-filter:blur(2px);background:#00000059;justify-content:center;align-items:center;padding:24px;display:flex;position:fixed;inset:0}._6VtqdG_dialog{background:var(--dsw-surface,#f5f5f7);min-width:min(320px,100%);max-width:380px;color:var(--dsw-text,#1d1d1f);border-radius:14px;padding:18px 18px 14px;box-shadow:0 12px 40px #00000047,0 2px 8px #00000029}@media (prefers-color-scheme:dark){._6VtqdG_dialog{color:#f5f5f7;background:#2c2c2e}}._6VtqdG_dialogTitle{letter-spacing:-.01em;margin:0 0 8px;font-size:15px;font-weight:700}._6VtqdG_dialogText{opacity:.65;margin:0 0 10px;font-size:12.5px;line-height:1.5}._6VtqdG_dialogList{flex-direction:column;gap:5px;max-height:200px;margin:0 0 14px;padding:0;list-style:none;display:flex;overflow:auto}._6VtqdG_dialogList li{opacity:.85;word-break:break-all;font-size:12.5px}._6VtqdG_dialogActions{justify-content:flex-end;gap:8px;display:flex}._6VtqdG_filterBar ._6VtqdG_search{flex:1;min-width:160px}._6VtqdG_filterBar ._6VtqdG_formLabel{flex:none}._6VtqdG_filterBar ._6VtqdG_select{flex:none;max-width:150px}._6VtqdG_filterBar ._6VtqdG_segmented{flex:none}._6VtqdG_titleIcon{vertical-align:-2px;opacity:.8;margin-right:2px;display:inline-block}._6VtqdG_grow{flex:1}._6VtqdG_hintLine{opacity:.55;margin:0;font-size:11.5px}._6VtqdG_hintPadded{margin:6px 12px 2px}._6VtqdG_hintInline{margin:4px 2px}._6VtqdG_rowMuted{cursor:default;opacity:.55}._6VtqdG_actionsPadded{padding:8px 12px}._6VtqdG_actionsTop{margin-top:8px}._6VtqdG_groupTime{margin-left:6px}._6VtqdG_sectionHeadRow{align-items:center;gap:8px;display:flex}._6VtqdG_sectionTitleFill{flex:1}._6VtqdG_dialogSelect{width:100%;margin-bottom:12px}._6VtqdG_dialogRow{align-items:center;gap:8px;display:flex}._6VtqdG_workspaceBox{align-items:center;gap:6px;display:inline-flex}._6VtqdG_workspaceInput{width:200px;padding:6px 10px;font-size:12px}._6VtqdG_projectNest{border-left:1px solid #80808024;flex-direction:column;margin:2px 0 0 14px;display:flex}._6VtqdG_dragHandle{cursor:grab;opacity:.28;letter-spacing:0;user-select:none;flex:none;justify-content:center;align-self:center;align-items:center;width:8px;height:16px;padding:0 1px;font-size:10px;display:inline-flex;overflow:hidden}._6VtqdG_dragHandle:active{cursor:grabbing}._6VtqdG_dragging{opacity:.45}._6VtqdG_dragOver{outline:2px dashed var(--hub-model,#2f81f7);outline-offset:-2px;background:#2f81f712}._6VtqdG_scanList{background:#8080800f;border:.5px solid #80808021;border-radius:12px;max-height:360px;margin-top:8px;overflow:auto}._6VtqdG_scanProgressTrack{background:#80808021;border-radius:4px;flex:1;height:6px;overflow:hidden}._6VtqdG_scanProgressFill{background:var(--hub-model,#2f81f7);height:100%;transition:width .2s}";
|
|
2470
2767
|
const tagId = "dsh-skill-hub/panel.module.css";
|
|
2471
2768
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
2472
2769
|
const tag = document.createElement("style");
|
|
@@ -2477,18 +2774,13 @@ window.__ModuleLoader__.load({
|
|
|
2477
2774
|
}
|
|
2478
2775
|
var panel_module_css_default = {
|
|
2479
2776
|
"actions": "_6VtqdG_actions",
|
|
2480
|
-
"actionsBottom": "_6VtqdG_actionsBottom",
|
|
2481
2777
|
"actionsPadded": "_6VtqdG_actionsPadded",
|
|
2482
2778
|
"actionsTop": "_6VtqdG_actionsTop",
|
|
2483
2779
|
"back": "_6VtqdG_back",
|
|
2484
2780
|
"badge": "_6VtqdG_badge",
|
|
2485
2781
|
"badgeCount": "_6VtqdG_badgeCount",
|
|
2486
|
-
"badgeDisabled": "_6VtqdG_badgeDisabled",
|
|
2487
|
-
"badgeModel": "_6VtqdG_badgeModel",
|
|
2488
2782
|
"badgeReadonly": "_6VtqdG_badgeReadonly",
|
|
2489
2783
|
"badgeSource": "_6VtqdG_badgeSource",
|
|
2490
|
-
"badgeUser": "_6VtqdG_badgeUser",
|
|
2491
|
-
"badgeUses": "_6VtqdG_badgeUses",
|
|
2492
2784
|
"badges": "_6VtqdG_badges",
|
|
2493
2785
|
"button": "_6VtqdG_button",
|
|
2494
2786
|
"buttons": "_6VtqdG_buttons",
|
|
@@ -2512,6 +2804,7 @@ window.__ModuleLoader__.load({
|
|
|
2512
2804
|
"dialogText": "_6VtqdG_dialogText",
|
|
2513
2805
|
"dialogTitle": "_6VtqdG_dialogTitle",
|
|
2514
2806
|
"disclosure": "_6VtqdG_disclosure",
|
|
2807
|
+
"displayName": "_6VtqdG_displayName",
|
|
2515
2808
|
"dot": "_6VtqdG_dot",
|
|
2516
2809
|
"dotModel": "_6VtqdG_dotModel",
|
|
2517
2810
|
"dotUser": "_6VtqdG_dotUser",
|
|
@@ -2555,7 +2848,6 @@ window.__ModuleLoader__.load({
|
|
|
2555
2848
|
"row": "_6VtqdG_row",
|
|
2556
2849
|
"rowDesc": "_6VtqdG_rowDesc",
|
|
2557
2850
|
"rowMain": "_6VtqdG_rowMain",
|
|
2558
|
-
"rowMeta": "_6VtqdG_rowMeta",
|
|
2559
2851
|
"rowMuted": "_6VtqdG_rowMuted",
|
|
2560
2852
|
"rowName": "_6VtqdG_rowName",
|
|
2561
2853
|
"rowNameText": "_6VtqdG_rowNameText",
|
|
@@ -2653,32 +2945,108 @@ window.__ModuleLoader__.load({
|
|
|
2653
2945
|
}
|
|
2654
2946
|
//#endregion
|
|
2655
2947
|
//#region src/client/panel/SkillDetailView.tsx
|
|
2948
|
+
/**
|
|
2949
|
+
* Full-page skill detail view: metadata, upstream source card with
|
|
2950
|
+
* check/sync/follow-delete actions, and the raw SKILL.md body. Pure
|
|
2951
|
+
* presentation — every state value and action arrives as a prop, so the
|
|
2952
|
+
* panel stays the single owner of state.
|
|
2953
|
+
*/
|
|
2656
2954
|
function SkillDetailView(props) {
|
|
2657
|
-
const { detail, hubConfig, uses, groupsState, sourcesState, sourceCheck, checkingSource, syncingSource, loading, onBack, onCheck, onSync, onFollowDelete } = props;
|
|
2955
|
+
const { detail, hubConfig, uses, groupsState, sourcesState, sourceCheck, checkingSource, syncingSource, loading, disabled, onEnable, onBack, onCheck, onSync, onFollowDelete } = props;
|
|
2658
2956
|
const detailSource = sourcesState?.sources.find((source) => source.skills.includes(detail.name));
|
|
2659
2957
|
const detailCheck = detailSource !== void 0 ? sourceCheck[detailSource.repo] : void 0;
|
|
2958
|
+
const [copied, setCopied] = (0, react.useState)(null);
|
|
2959
|
+
const copyTimer = (0, react.useRef)(void 0);
|
|
2960
|
+
(0, react.useEffect)(() => () => {
|
|
2961
|
+
window.clearTimeout(copyTimer.current);
|
|
2962
|
+
}, []);
|
|
2963
|
+
const copyText = (text, key) => {
|
|
2964
|
+
copyTextToClipboard(text).then((ok) => {
|
|
2965
|
+
if (!ok) return;
|
|
2966
|
+
setCopied(key);
|
|
2967
|
+
window.clearTimeout(copyTimer.current);
|
|
2968
|
+
copyTimer.current = window.setTimeout(() => {
|
|
2969
|
+
setCopied(null);
|
|
2970
|
+
}, 1200);
|
|
2971
|
+
});
|
|
2972
|
+
};
|
|
2660
2973
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2661
2974
|
className: panel_module_css_default.panel,
|
|
2662
2975
|
children: [
|
|
2663
2976
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2664
2977
|
className: panel_module_css_default.detailHead,
|
|
2665
|
-
children: [
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
className: panel_module_css_default.
|
|
2674
|
-
style:
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2978
|
+
children: [
|
|
2979
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2980
|
+
type: "button",
|
|
2981
|
+
className: panel_module_css_default.back,
|
|
2982
|
+
onClick: onBack,
|
|
2983
|
+
children: tt("detail.back")
|
|
2984
|
+
}),
|
|
2985
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2986
|
+
className: panel_module_css_default.detailName,
|
|
2987
|
+
style: {
|
|
2988
|
+
display: "inline-flex",
|
|
2989
|
+
alignItems: "center",
|
|
2990
|
+
gap: 6,
|
|
2991
|
+
flexWrap: "wrap"
|
|
2992
|
+
},
|
|
2993
|
+
children: [
|
|
2994
|
+
detail.name,
|
|
2995
|
+
isDisplayNameDistinct(detail.name, detail.displayName) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2996
|
+
className: panel_module_css_default.displayName,
|
|
2997
|
+
style: {
|
|
2998
|
+
fontSize: 13,
|
|
2999
|
+
marginLeft: 0
|
|
3000
|
+
},
|
|
3001
|
+
children: detail.displayName
|
|
3002
|
+
}) : null,
|
|
3003
|
+
detail.invocation.modelInvocable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3004
|
+
className: panel_module_css_default.dot + " " + panel_module_css_default.dotModel,
|
|
3005
|
+
style: dotStyle(hubConfig?.dotModelColor),
|
|
3006
|
+
title: tt("legend.model")
|
|
3007
|
+
}) : detail.invocation.userInvocable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3008
|
+
className: panel_module_css_default.dot + " " + panel_module_css_default.dotUser,
|
|
3009
|
+
style: dotStyle(hubConfig?.dotUserColor),
|
|
3010
|
+
title: tt("legend.user")
|
|
3011
|
+
}) : null
|
|
3012
|
+
]
|
|
3013
|
+
}),
|
|
3014
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3015
|
+
className: panel_module_css_default.actions,
|
|
3016
|
+
style: {
|
|
3017
|
+
marginLeft: "auto",
|
|
3018
|
+
gap: 6
|
|
3019
|
+
},
|
|
3020
|
+
children: [
|
|
3021
|
+
disabled === true && onEnable !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3022
|
+
type: "button",
|
|
3023
|
+
className: panel_module_css_default.opBtn,
|
|
3024
|
+
role: "switch",
|
|
3025
|
+
"aria-checked": false,
|
|
3026
|
+
"aria-label": tt("row.enable"),
|
|
3027
|
+
onClick: onEnable,
|
|
3028
|
+
children: tt("row.enable")
|
|
3029
|
+
}) : null,
|
|
3030
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3031
|
+
type: "button",
|
|
3032
|
+
className: panel_module_css_default.opBtn,
|
|
3033
|
+
onClick: () => {
|
|
3034
|
+
copyText("$" + detail.name, "mention");
|
|
3035
|
+
},
|
|
3036
|
+
children: copied === "mention" ? tt("detail.copied") : tt("detail.copyMention")
|
|
3037
|
+
}),
|
|
3038
|
+
detail.path !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3039
|
+
type: "button",
|
|
3040
|
+
className: panel_module_css_default.opBtn,
|
|
3041
|
+
onClick: () => {
|
|
3042
|
+
const path = detail.path;
|
|
3043
|
+
if (path !== void 0) copyText(path, "path");
|
|
3044
|
+
},
|
|
3045
|
+
children: copied === "path" ? tt("detail.copied") : tt("detail.copyPath")
|
|
3046
|
+
}) : null
|
|
3047
|
+
]
|
|
3048
|
+
})
|
|
3049
|
+
]
|
|
2682
3050
|
}),
|
|
2683
3051
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2684
3052
|
className: panel_module_css_default.detailMeta,
|
|
@@ -2780,34 +3148,30 @@ window.__ModuleLoader__.load({
|
|
|
2780
3148
|
}),
|
|
2781
3149
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2782
3150
|
className: panel_module_css_default.buttons + " " + panel_module_css_default.actionsTop,
|
|
2783
|
-
children: [
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
},
|
|
2808
|
-
children: tt("source.followDelete")
|
|
2809
|
-
}) : null
|
|
2810
|
-
]
|
|
3151
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3152
|
+
type: "button",
|
|
3153
|
+
className: panel_module_css_default.opBtn,
|
|
3154
|
+
disabled: checkingSource !== null,
|
|
3155
|
+
onClick: () => {
|
|
3156
|
+
onCheck(detailSource.repo);
|
|
3157
|
+
},
|
|
3158
|
+
children: checkingSource === detailSource.repo ? tt("source.checking") : tt("source.check")
|
|
3159
|
+
}), disabled !== true ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3160
|
+
type: "button",
|
|
3161
|
+
className: panel_module_css_default.opBtn,
|
|
3162
|
+
disabled: syncingSource !== null,
|
|
3163
|
+
onClick: () => {
|
|
3164
|
+
onSync(detailSource.repo, [detail.name]);
|
|
3165
|
+
},
|
|
3166
|
+
children: syncingSource === detailSource.repo ? tt("source.syncing") : tt("source.sync")
|
|
3167
|
+
}), detailCheck?.deleted.includes(detail.name) === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3168
|
+
type: "button",
|
|
3169
|
+
className: panel_module_css_default.opBtn + " " + panel_module_css_default.opDanger,
|
|
3170
|
+
onClick: () => {
|
|
3171
|
+
onFollowDelete(detailSource.repo, [detail.name]);
|
|
3172
|
+
},
|
|
3173
|
+
children: tt("source.followDelete")
|
|
3174
|
+
}) : null] }) : null]
|
|
2811
3175
|
})
|
|
2812
3176
|
]
|
|
2813
3177
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -2944,6 +3308,7 @@ window.__ModuleLoader__.load({
|
|
|
2944
3308
|
const stat = hub.uses.get(skill.name);
|
|
2945
3309
|
const count = stat?.count ?? 0;
|
|
2946
3310
|
const lastUsed = stat?.lastUsed;
|
|
3311
|
+
const isDuplicate = hub.catalog?.duplicateNames?.includes(skill.name) === true;
|
|
2947
3312
|
/** 单一状态圆点:模型可调 → 蓝;否则用户可调 → 绿。与聊天 / 菜单同规则。 */
|
|
2948
3313
|
const dot = skill.invocation.modelInvocable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2949
3314
|
className: panel_module_css_default.dot + " " + panel_module_css_default.dotModel,
|
|
@@ -2980,11 +3345,21 @@ window.__ModuleLoader__.load({
|
|
|
2980
3345
|
className: panel_module_css_default.rowNameText,
|
|
2981
3346
|
children: skill.name
|
|
2982
3347
|
}),
|
|
3348
|
+
isDisplayNameDistinct(skill.name, skill.displayName) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3349
|
+
className: panel_module_css_default.displayName,
|
|
3350
|
+
title: skill.displayName,
|
|
3351
|
+
children: skill.displayName
|
|
3352
|
+
}) : null,
|
|
2983
3353
|
count > 0 && hub.hubConfig?.showUseCount !== false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2984
3354
|
className: panel_module_css_default.useCount,
|
|
2985
3355
|
children: count
|
|
2986
3356
|
}) : null,
|
|
2987
3357
|
dot,
|
|
3358
|
+
isDuplicate ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3359
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.statusError,
|
|
3360
|
+
title: tt("row.duplicateHint"),
|
|
3361
|
+
children: tt("row.duplicate")
|
|
3362
|
+
}) : null,
|
|
2988
3363
|
lastUsed !== void 0 && hub.hubConfig?.showUseTime !== false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2989
3364
|
className: panel_module_css_default.useTime,
|
|
2990
3365
|
children: relativeTimeText(lastUsed)
|
|
@@ -2992,7 +3367,8 @@ window.__ModuleLoader__.load({
|
|
|
2992
3367
|
]
|
|
2993
3368
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2994
3369
|
className: panel_module_css_default.rowDesc,
|
|
2995
|
-
|
|
3370
|
+
title: skill.description,
|
|
3371
|
+
children: skill.shortDescription ?? skill.description
|
|
2996
3372
|
})]
|
|
2997
3373
|
}), skill.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2998
3374
|
type: "button",
|
|
@@ -3030,17 +3406,36 @@ window.__ModuleLoader__.load({
|
|
|
3030
3406
|
return tt("badge.source." + root);
|
|
3031
3407
|
}
|
|
3032
3408
|
function DisabledRow(props) {
|
|
3033
|
-
const { record, busy, onEnable } = props;
|
|
3409
|
+
const { record, busy, onEnable, duplicate, onOpen } = props;
|
|
3410
|
+
/** 键盘打开详情:Enter 或空格。仅当焦点在行本身(而非行内开关)时生效。 */
|
|
3411
|
+
const onKeyDown = (event) => {
|
|
3412
|
+
if (onOpen === void 0 || event.target !== event.currentTarget) return;
|
|
3413
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
3414
|
+
event.preventDefault();
|
|
3415
|
+
onOpen();
|
|
3416
|
+
}
|
|
3417
|
+
};
|
|
3034
3418
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3035
|
-
className: panel_module_css_default.row + " " + panel_module_css_default.rowStatic,
|
|
3419
|
+
className: panel_module_css_default.row + (onOpen === void 0 ? " " + panel_module_css_default.rowStatic : ""),
|
|
3420
|
+
role: onOpen === void 0 ? void 0 : "button",
|
|
3421
|
+
tabIndex: onOpen === void 0 ? void 0 : 0,
|
|
3422
|
+
"aria-label": onOpen === void 0 ? void 0 : tt("row.open", { name: record.name }),
|
|
3423
|
+
onClick: onOpen,
|
|
3424
|
+
onKeyDown,
|
|
3036
3425
|
children: [
|
|
3037
3426
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3038
3427
|
className: panel_module_css_default.rowMain,
|
|
3039
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
3428
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3040
3429
|
className: panel_module_css_default.rowName,
|
|
3041
|
-
children: record.name
|
|
3430
|
+
children: [record.name, duplicate === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3431
|
+
className: panel_module_css_default.badge + " " + panel_module_css_default.statusError,
|
|
3432
|
+
style: { marginLeft: 6 },
|
|
3433
|
+
title: tt("row.duplicateHint"),
|
|
3434
|
+
children: tt("row.duplicate")
|
|
3435
|
+
}) : null]
|
|
3042
3436
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3043
3437
|
className: panel_module_css_default.rowDesc,
|
|
3438
|
+
title: record.description,
|
|
3044
3439
|
children: record.description
|
|
3045
3440
|
})]
|
|
3046
3441
|
}),
|
|
@@ -3058,7 +3453,10 @@ window.__ModuleLoader__.load({
|
|
|
3058
3453
|
"aria-label": tt("row.enable"),
|
|
3059
3454
|
className: panel_module_css_default.switch,
|
|
3060
3455
|
disabled: busy,
|
|
3061
|
-
onClick:
|
|
3456
|
+
onClick: (event) => {
|
|
3457
|
+
event.stopPropagation();
|
|
3458
|
+
onEnable();
|
|
3459
|
+
},
|
|
3062
3460
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.switchThumb })
|
|
3063
3461
|
})
|
|
3064
3462
|
]
|
|
@@ -3415,8 +3813,12 @@ window.__ModuleLoader__.load({
|
|
|
3415
3813
|
}, skill.name)), disabledMembers.map((record) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DisabledRow, {
|
|
3416
3814
|
record,
|
|
3417
3815
|
busy: busyNames.has(record.name),
|
|
3816
|
+
duplicate: catalog?.duplicateNames?.includes(record.name) === true,
|
|
3418
3817
|
onEnable: () => {
|
|
3419
3818
|
enableDisabled(record);
|
|
3819
|
+
},
|
|
3820
|
+
onOpen: () => {
|
|
3821
|
+
hub.openDetail(record.name);
|
|
3420
3822
|
}
|
|
3421
3823
|
}, record.name))] }) : null]
|
|
3422
3824
|
}, "col:" + collection.name);
|
|
@@ -3502,8 +3904,12 @@ window.__ModuleLoader__.load({
|
|
|
3502
3904
|
}, skill.name)), personalDisabled.map((record) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DisabledRow, {
|
|
3503
3905
|
record,
|
|
3504
3906
|
busy: busyNames.has(record.name),
|
|
3907
|
+
duplicate: catalog?.duplicateNames?.includes(record.name) === true,
|
|
3505
3908
|
onEnable: () => {
|
|
3506
3909
|
enableDisabled(record);
|
|
3910
|
+
},
|
|
3911
|
+
onOpen: () => {
|
|
3912
|
+
hub.openDetail(record.name);
|
|
3507
3913
|
}
|
|
3508
3914
|
}, record.name))] }) : null]
|
|
3509
3915
|
}, "uncategorized-source");
|
|
@@ -3657,8 +4063,12 @@ window.__ModuleLoader__.load({
|
|
|
3657
4063
|
}, skill.name)), disabledMembers.map((record) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DisabledRow, {
|
|
3658
4064
|
record,
|
|
3659
4065
|
busy: busyNames.has(record.name),
|
|
4066
|
+
duplicate: hub.catalog?.duplicateNames?.includes(record.name) === true,
|
|
3660
4067
|
onEnable: () => {
|
|
3661
4068
|
enableDisabled(record);
|
|
4069
|
+
},
|
|
4070
|
+
onOpen: () => {
|
|
4071
|
+
hub.openDetail(record.name);
|
|
3662
4072
|
}
|
|
3663
4073
|
}, record.name))] }) : null]
|
|
3664
4074
|
}, "tag:" + tag.id);
|
|
@@ -3803,23 +4213,106 @@ window.__ModuleLoader__.load({
|
|
|
3803
4213
|
children: [
|
|
3804
4214
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
3805
4215
|
className: panel_module_css_default.dialogTitle,
|
|
3806
|
-
children: tt("market.branchTitle")
|
|
4216
|
+
children: tt("market.branchTitle")
|
|
4217
|
+
}),
|
|
4218
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4219
|
+
className: panel_module_css_default.dialogText,
|
|
4220
|
+
children: tt("market.branchHint")
|
|
4221
|
+
}),
|
|
4222
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
4223
|
+
className: panel_module_css_default.select + " " + panel_module_css_default.dialogSelect,
|
|
4224
|
+
value: choice.selected,
|
|
4225
|
+
onChange: (event) => {
|
|
4226
|
+
onSelect(event.target.value);
|
|
4227
|
+
},
|
|
4228
|
+
children: choice.branches.map((branch) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
4229
|
+
value: branch,
|
|
4230
|
+
children: branch
|
|
4231
|
+
}, branch))
|
|
4232
|
+
}),
|
|
4233
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4234
|
+
className: panel_module_css_default.dialogActions,
|
|
4235
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4236
|
+
type: "button",
|
|
4237
|
+
className: panel_module_css_default.button,
|
|
4238
|
+
onClick: onCancel,
|
|
4239
|
+
children: tt("form.cancel")
|
|
4240
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4241
|
+
type: "button",
|
|
4242
|
+
className: panel_module_css_default.button + " " + panel_module_css_default.primary,
|
|
4243
|
+
disabled: busy,
|
|
4244
|
+
onClick: onConfirm,
|
|
4245
|
+
children: tt("market.branchConfirm")
|
|
4246
|
+
})]
|
|
4247
|
+
})
|
|
4248
|
+
]
|
|
4249
|
+
});
|
|
4250
|
+
}
|
|
4251
|
+
/** Version picker: switch a market source's tracked ref (release / branch / custom). */
|
|
4252
|
+
function VersionChoiceDialog(props) {
|
|
4253
|
+
const { choice, busy, onSelect, onCustom, onCancel, onConfirm } = props;
|
|
4254
|
+
const effective = choice.custom.trim() !== "" ? choice.custom.trim() : choice.selected;
|
|
4255
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogShell, {
|
|
4256
|
+
onClose: onCancel,
|
|
4257
|
+
children: [
|
|
4258
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
4259
|
+
className: panel_module_css_default.dialogTitle,
|
|
4260
|
+
children: tt("market.versionTitle")
|
|
3807
4261
|
}),
|
|
3808
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
4262
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
3809
4263
|
className: panel_module_css_default.dialogText,
|
|
3810
|
-
children: tt("market.
|
|
3811
|
-
}),
|
|
3812
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
3813
|
-
className: panel_module_css_default.select + " " + panel_module_css_default.dialogSelect,
|
|
3814
|
-
value: choice.selected,
|
|
3815
|
-
onChange: (event) => {
|
|
3816
|
-
onSelect(event.target.value);
|
|
3817
|
-
},
|
|
3818
|
-
children: choice.branches.map((branch) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3819
|
-
value: branch,
|
|
3820
|
-
children: branch
|
|
3821
|
-
}, branch))
|
|
4264
|
+
children: [tt("market.versionText", { repo: choice.repo }), choice.current !== void 0 ? ` (${tt("market.versionCurrent", { ref: choice.current })})` : ""]
|
|
3822
4265
|
}),
|
|
4266
|
+
choice.loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4267
|
+
className: panel_module_css_default.dialogText,
|
|
4268
|
+
children: tt("market.scanning")
|
|
4269
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
4270
|
+
choice.releases.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4271
|
+
className: panel_module_css_default.dialogText,
|
|
4272
|
+
style: { marginBottom: 4 },
|
|
4273
|
+
children: tt("market.versionReleases")
|
|
4274
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
4275
|
+
className: panel_module_css_default.select + " " + panel_module_css_default.dialogSelect,
|
|
4276
|
+
value: choice.releases.includes(choice.selected) ? choice.selected : choice.releases[0],
|
|
4277
|
+
onChange: (event) => {
|
|
4278
|
+
onSelect(event.target.value);
|
|
4279
|
+
onCustom("");
|
|
4280
|
+
},
|
|
4281
|
+
children: choice.releases.map((tag) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
4282
|
+
value: tag,
|
|
4283
|
+
children: tag
|
|
4284
|
+
}, tag))
|
|
4285
|
+
})] }) : null,
|
|
4286
|
+
choice.branches.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4287
|
+
className: panel_module_css_default.dialogText,
|
|
4288
|
+
style: { marginBottom: 4 },
|
|
4289
|
+
children: tt("market.versionBranches")
|
|
4290
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
4291
|
+
className: panel_module_css_default.select + " " + panel_module_css_default.dialogSelect,
|
|
4292
|
+
value: choice.branches.includes(choice.selected) ? choice.selected : choice.branches[0],
|
|
4293
|
+
onChange: (event) => {
|
|
4294
|
+
onSelect(event.target.value);
|
|
4295
|
+
onCustom("");
|
|
4296
|
+
},
|
|
4297
|
+
children: choice.branches.map((branch) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
4298
|
+
value: branch,
|
|
4299
|
+
children: branch
|
|
4300
|
+
}, branch))
|
|
4301
|
+
})] }) : null,
|
|
4302
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4303
|
+
className: panel_module_css_default.dialogText,
|
|
4304
|
+
style: { marginBottom: 4 },
|
|
4305
|
+
children: tt("market.versionCustom")
|
|
4306
|
+
}),
|
|
4307
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4308
|
+
className: panel_module_css_default.input + " " + panel_module_css_default.dialogSelect,
|
|
4309
|
+
value: choice.custom,
|
|
4310
|
+
onChange: (event) => {
|
|
4311
|
+
onCustom(event.target.value);
|
|
4312
|
+
},
|
|
4313
|
+
placeholder: "v1.2.3 / main / abc1234"
|
|
4314
|
+
})
|
|
4315
|
+
] }),
|
|
3823
4316
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3824
4317
|
className: panel_module_css_default.dialogActions,
|
|
3825
4318
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -3830,9 +4323,9 @@ window.__ModuleLoader__.load({
|
|
|
3830
4323
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3831
4324
|
type: "button",
|
|
3832
4325
|
className: panel_module_css_default.button + " " + panel_module_css_default.primary,
|
|
3833
|
-
disabled: busy,
|
|
4326
|
+
disabled: busy || choice.loading || effective === "",
|
|
3834
4327
|
onClick: onConfirm,
|
|
3835
|
-
children: tt("market.
|
|
4328
|
+
children: busy ? tt("source.syncing") : tt("market.versionConfirm", { ref: effective })
|
|
3836
4329
|
})]
|
|
3837
4330
|
})
|
|
3838
4331
|
]
|
|
@@ -3978,10 +4471,19 @@ window.__ModuleLoader__.load({
|
|
|
3978
4471
|
rel: "noreferrer",
|
|
3979
4472
|
children: record.repo
|
|
3980
4473
|
}),
|
|
3981
|
-
|
|
4474
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4475
|
+
type: "button",
|
|
3982
4476
|
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeSource,
|
|
3983
|
-
|
|
3984
|
-
|
|
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
|
+
}),
|
|
3985
4487
|
installedCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3986
4488
|
className: panel_module_css_default.badge + " " + panel_module_css_default.badgeCount,
|
|
3987
4489
|
children: tt("market.installed", { count: installedCount })
|
|
@@ -4230,279 +4732,291 @@ window.__ModuleLoader__.load({
|
|
|
4230
4732
|
return true;
|
|
4231
4733
|
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
4232
4734
|
const paged = filtered.slice(0, visibleCount);
|
|
4233
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
(() => {
|
|
4245
|
-
const order = /* @__PURE__ */ new Map([["skills", 0], ["design-templates", 1]]);
|
|
4246
|
-
const roots = [...new Set(entries.map((e) => e.root))].sort((a, b) => {
|
|
4247
|
-
const ao = order.get(a) ?? 99;
|
|
4248
|
-
const bo = order.get(b) ?? 99;
|
|
4249
|
-
return ao !== bo ? ao - bo : a.localeCompare(b);
|
|
4250
|
-
});
|
|
4251
|
-
const counts = /* @__PURE__ */ new Map();
|
|
4252
|
-
for (const e of entries) counts.set(e.root, (counts.get(e.root) ?? 0) + 1);
|
|
4253
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4254
|
-
style: {
|
|
4255
|
-
display: "flex",
|
|
4256
|
-
gap: 8,
|
|
4257
|
-
marginBottom: 8,
|
|
4258
|
-
flexWrap: "wrap"
|
|
4259
|
-
},
|
|
4260
|
-
children: [
|
|
4261
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4262
|
-
className: panel_module_css_default.input,
|
|
4263
|
-
style: {
|
|
4264
|
-
flex: 1,
|
|
4265
|
-
minWidth: 140
|
|
4266
|
-
},
|
|
4267
|
-
value: repoSearch,
|
|
4268
|
-
onChange: (e) => setRepoSearch(e.target.value),
|
|
4269
|
-
placeholder: "搜索技能名…"
|
|
4270
|
-
}),
|
|
4271
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4272
|
-
type: "button",
|
|
4273
|
-
className: panel_module_css_default.button + (repoFilter === "all" ? " " + panel_module_css_default.primary : ""),
|
|
4274
|
-
style: {
|
|
4275
|
-
padding: "6px 10px",
|
|
4276
|
-
fontSize: 12
|
|
4277
|
-
},
|
|
4278
|
-
onClick: () => setRepoFilter("all"),
|
|
4279
|
-
children: ["全部 ", entries.length]
|
|
4280
|
-
}),
|
|
4281
|
-
roots.map((root) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4282
|
-
type: "button",
|
|
4283
|
-
className: panel_module_css_default.button + (repoFilter === root ? " " + panel_module_css_default.primary : ""),
|
|
4284
|
-
style: {
|
|
4285
|
-
padding: "6px 10px",
|
|
4286
|
-
fontSize: 12
|
|
4287
|
-
},
|
|
4288
|
-
onClick: () => setRepoFilter(root),
|
|
4289
|
-
children: [
|
|
4290
|
-
root,
|
|
4291
|
-
" ",
|
|
4292
|
-
counts.get(root) ?? 0
|
|
4293
|
-
]
|
|
4294
|
-
}, root))
|
|
4295
|
-
]
|
|
4296
|
-
});
|
|
4297
|
-
})(),
|
|
4298
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4299
|
-
className: panel_module_css_default.hintLine,
|
|
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,
|
|
4300
4746
|
style: {
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
gap: 8
|
|
4747
|
+
background: "rgba(210,153,34,0.08)",
|
|
4748
|
+
color: "#d29922",
|
|
4749
|
+
borderLeft: "3px solid #d29922"
|
|
4305
4750
|
},
|
|
4306
|
-
children:
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
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,
|
|
4319
4813
|
style: {
|
|
4320
4814
|
display: "flex",
|
|
4321
|
-
|
|
4815
|
+
justifyContent: "space-between",
|
|
4816
|
+
flexWrap: "wrap",
|
|
4817
|
+
gap: 8
|
|
4322
4818
|
},
|
|
4323
|
-
children: [
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
padding: "4px 8px",
|
|
4341
|
-
fontSize: 11
|
|
4342
|
-
},
|
|
4343
|
-
onClick: () => {
|
|
4344
|
-
setRepoSelected(new Set(entries.filter((e) => !isExisting(e)).map((e) => e.path)));
|
|
4345
|
-
},
|
|
4346
|
-
children: ["全选全部 ", entries.filter((e) => !isExisting(e)).length]
|
|
4347
|
-
}),
|
|
4348
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4349
|
-
type: "button",
|
|
4350
|
-
className: panel_module_css_default.button,
|
|
4351
|
-
style: {
|
|
4352
|
-
padding: "4px 8px",
|
|
4353
|
-
fontSize: 11
|
|
4354
|
-
},
|
|
4355
|
-
onClick: () => {
|
|
4356
|
-
setRepoSelected(/* @__PURE__ */ new Set());
|
|
4357
|
-
},
|
|
4358
|
-
children: tt("repo.clearAll")
|
|
4359
|
-
})
|
|
4360
|
-
]
|
|
4361
|
-
})]
|
|
4362
|
-
}),
|
|
4363
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4364
|
-
className: panel_module_css_default.scanList,
|
|
4365
|
-
children: paged.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4366
|
-
className: panel_module_css_default.empty,
|
|
4367
|
-
style: { padding: 20 },
|
|
4368
|
-
children: "无匹配技能"
|
|
4369
|
-
}) : paged.map((entry) => {
|
|
4370
|
-
const existing = isExisting(entry);
|
|
4371
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
4372
|
-
className: panel_module_css_default.row + (existing ? " " + panel_module_css_default.rowMuted : ""),
|
|
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
|
+
},
|
|
4373
4836
|
children: [
|
|
4374
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
4375
|
-
type: "
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
}
|
|
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: "全选当前显示"
|
|
4381
4848
|
}),
|
|
4382
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
count: entry.fileCount,
|
|
4394
|
-
size: formatBytes(entry.totalBytes)
|
|
4395
|
-
})
|
|
4396
|
-
]
|
|
4397
|
-
})]
|
|
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]
|
|
4398
4860
|
}),
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
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")
|
|
4405
4872
|
})
|
|
4406
4873
|
]
|
|
4407
|
-
}
|
|
4408
|
-
})
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
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
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4452
|
-
style: {
|
|
4453
|
-
display: "flex",
|
|
4454
|
-
alignItems: "center",
|
|
4455
|
-
gap: 8,
|
|
4456
|
-
marginBottom: 6
|
|
4457
|
-
},
|
|
4458
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4459
|
-
className: panel_module_css_default.scanProgressTrack,
|
|
4460
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4461
|
-
className: panel_module_css_default.scanProgressFill,
|
|
4462
|
-
style: { width: `${pct}%` }
|
|
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
|
|
4463
4918
|
})
|
|
4464
|
-
|
|
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", {
|
|
4465
4965
|
style: {
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4966
|
+
display: "flex",
|
|
4967
|
+
alignItems: "center",
|
|
4968
|
+
gap: 8,
|
|
4969
|
+
marginBottom: 6
|
|
4470
4970
|
},
|
|
4471
|
-
children: [
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
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
|
+
] });
|
|
4506
5020
|
})() : null,
|
|
4507
5021
|
repoResult !== null && repoResult.status !== "running" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4508
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,
|
|
@@ -4609,6 +5123,7 @@ window.__ModuleLoader__.load({
|
|
|
4609
5123
|
const [tab, setTab] = (0, react.useState)("sources");
|
|
4610
5124
|
const [skillView, setSkillView] = (0, react.useState)("groups");
|
|
4611
5125
|
const [sourceFilter, setSourceFilter] = (0, react.useState)("all");
|
|
5126
|
+
const [invocationFilter, setInvocationFilter] = (0, react.useState)("all");
|
|
4612
5127
|
const [sortKey, setSortKey] = (0, react.useState)("name");
|
|
4613
5128
|
const [marketState, setMarketState] = (0, react.useState)({
|
|
4614
5129
|
status: "loading",
|
|
@@ -4639,8 +5154,9 @@ window.__ModuleLoader__.load({
|
|
|
4639
5154
|
const [newTagName, setNewTagName] = (0, react.useState)("");
|
|
4640
5155
|
const [tagBusy, setTagBusy] = (0, react.useState)(false);
|
|
4641
5156
|
const [editSearch, setEditSearch] = (0, react.useState)("");
|
|
4642
|
-
/** 分组视图里收起的分组(key 为 tag:<id>、col:<name> 或 project
|
|
5157
|
+
/** 分组视图里收起的分组(key 为 tag:<id>、col:<name> 或 project 树键)。技能总数 >80 时首次加载自动折叠 personal。 */
|
|
4643
5158
|
const [collapsedGroups, setCollapsedGroups] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
5159
|
+
const autoCollapsedPersonal = (0, react.useRef)(false);
|
|
4644
5160
|
/** 项目级三级树里已细分(按 .dsh/.agents)的项目键。 */
|
|
4645
5161
|
const [subdividedProjects, setSubdividedProjects] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
4646
5162
|
const [showLegend, setShowLegend] = (0, react.useState)(false);
|
|
@@ -4665,6 +5181,10 @@ window.__ModuleLoader__.load({
|
|
|
4665
5181
|
try {
|
|
4666
5182
|
const next = await api.catalog(workspace !== "" ? { cwd: workspace } : void 0);
|
|
4667
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
|
+
}
|
|
4668
5188
|
setLoadError(null);
|
|
4669
5189
|
} catch (error) {
|
|
4670
5190
|
setLoadError(errorMessage(error));
|
|
@@ -4991,6 +5511,25 @@ window.__ModuleLoader__.load({
|
|
|
4991
5511
|
load,
|
|
4992
5512
|
loadSources
|
|
4993
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;
|
|
5530
|
+
});
|
|
5531
|
+
}
|
|
5532
|
+
}, [api, load]);
|
|
4994
5533
|
/** 打开单个技能的删除确认(移入回收站,可恢复)。 */
|
|
4995
5534
|
const requestDeleteSkill = (0, react.useCallback)((name) => {
|
|
4996
5535
|
setDeleteSkillDialog(name);
|
|
@@ -5274,6 +5813,7 @@ window.__ModuleLoader__.load({
|
|
|
5274
5813
|
status: "ready",
|
|
5275
5814
|
data
|
|
5276
5815
|
});
|
|
5816
|
+
loadMarket();
|
|
5277
5817
|
} catch (error) {
|
|
5278
5818
|
setRepoDiscoverState({
|
|
5279
5819
|
status: "error",
|
|
@@ -5282,7 +5822,7 @@ window.__ModuleLoader__.load({
|
|
|
5282
5822
|
} finally {
|
|
5283
5823
|
setScanningRepo(null);
|
|
5284
5824
|
}
|
|
5285
|
-
}, [api]);
|
|
5825
|
+
}, [api, loadMarket]);
|
|
5286
5826
|
/** 确认分支选择:持久化 ref 后重新扫描。 */
|
|
5287
5827
|
const confirmBranchChoice = (0, react.useCallback)(async () => {
|
|
5288
5828
|
if (branchChoice === null) return;
|
|
@@ -5302,6 +5842,59 @@ window.__ModuleLoader__.load({
|
|
|
5302
5842
|
branchChoice,
|
|
5303
5843
|
scanRepo
|
|
5304
5844
|
]);
|
|
5845
|
+
/** 版本对话框:打开时拉 releases + branches,确认后定版并重扫。 */
|
|
5846
|
+
const [versionDialog, setVersionDialog] = (0, react.useState)(null);
|
|
5847
|
+
const [versionBusy, setVersionBusy] = (0, react.useState)(false);
|
|
5848
|
+
const openVersionDialog = (0, react.useCallback)(async (repo) => {
|
|
5849
|
+
const current = marketState.repos.find((item) => item.repo === repo)?.ref;
|
|
5850
|
+
setVersionDialog({
|
|
5851
|
+
repo,
|
|
5852
|
+
...current !== void 0 ? { current } : {},
|
|
5853
|
+
releases: [],
|
|
5854
|
+
branches: [],
|
|
5855
|
+
selected: current ?? "",
|
|
5856
|
+
custom: "",
|
|
5857
|
+
loading: true
|
|
5858
|
+
});
|
|
5859
|
+
setLoadError(null);
|
|
5860
|
+
try {
|
|
5861
|
+
const data = await api.marketSourceVersions(repo);
|
|
5862
|
+
setVersionDialog({
|
|
5863
|
+
repo,
|
|
5864
|
+
...data.current !== void 0 ? { current: data.current } : {},
|
|
5865
|
+
releases: data.releases,
|
|
5866
|
+
branches: data.branches,
|
|
5867
|
+
selected: data.current ?? data.releases[0] ?? data.branches[0] ?? "",
|
|
5868
|
+
custom: "",
|
|
5869
|
+
loading: false
|
|
5870
|
+
});
|
|
5871
|
+
} catch (error) {
|
|
5872
|
+
setLoadError(errorMessage(error));
|
|
5873
|
+
setVersionDialog(null);
|
|
5874
|
+
}
|
|
5875
|
+
}, [api, marketState]);
|
|
5876
|
+
const confirmVersionDialog = (0, react.useCallback)(async () => {
|
|
5877
|
+
if (versionDialog === null) return;
|
|
5878
|
+
const ref = versionDialog.custom.trim() !== "" ? versionDialog.custom.trim() : versionDialog.selected;
|
|
5879
|
+
if (ref === "") return;
|
|
5880
|
+
setVersionBusy(true);
|
|
5881
|
+
setLoadError(null);
|
|
5882
|
+
try {
|
|
5883
|
+
await api.setMarketSourceRef(versionDialog.repo, ref);
|
|
5884
|
+
setVersionDialog(null);
|
|
5885
|
+
await loadMarket();
|
|
5886
|
+
await scanRepo(versionDialog.repo);
|
|
5887
|
+
} catch (error) {
|
|
5888
|
+
setLoadError(errorMessage(error));
|
|
5889
|
+
} finally {
|
|
5890
|
+
setVersionBusy(false);
|
|
5891
|
+
}
|
|
5892
|
+
}, [
|
|
5893
|
+
api,
|
|
5894
|
+
versionDialog,
|
|
5895
|
+
loadMarket,
|
|
5896
|
+
scanRepo
|
|
5897
|
+
]);
|
|
5305
5898
|
/** Toggle one repo preview row. */
|
|
5306
5899
|
const toggleRepoSelected = (0, react.useCallback)((path, checked) => {
|
|
5307
5900
|
setRepoSelected((previous) => {
|
|
@@ -5584,7 +6177,18 @@ window.__ModuleLoader__.load({
|
|
|
5584
6177
|
const hasPrivate = skills.some((skill) => origins[skill.name] === void 0 && !isProjectSource(skill.source));
|
|
5585
6178
|
return [...repos, ...hasPrivate ? [PRIVATE_SOURCE] : []];
|
|
5586
6179
|
}, [catalog, origins]);
|
|
5587
|
-
const filtered = (0, react.useMemo)(() => (catalog?.skills ?? []).filter((skill) =>
|
|
6180
|
+
const filtered = (0, react.useMemo)(() => (catalog?.skills ?? []).filter((skill) => {
|
|
6181
|
+
if (invocationFilter === "model" && !skill.invocation.modelInvocable) return false;
|
|
6182
|
+
if (invocationFilter === "user" && !skill.invocation.userInvocable) return false;
|
|
6183
|
+
if (normalized.length === 0) return true;
|
|
6184
|
+
return skill.name.toLocaleLowerCase().includes(normalized) || skill.description.toLocaleLowerCase().includes(normalized) || skill.displayName?.toLocaleLowerCase().includes(normalized) || skill.shortDescription?.toLocaleLowerCase().includes(normalized);
|
|
6185
|
+
}), [
|
|
6186
|
+
catalog,
|
|
6187
|
+
normalized,
|
|
6188
|
+
invocationFilter
|
|
6189
|
+
]);
|
|
6190
|
+
/** Rows actually rendered with a shortened description (shortDescription in use). */
|
|
6191
|
+
const shortenedCount = (0, react.useMemo)(() => filtered.filter((skill) => skill.shortDescription !== void 0).length, [filtered]);
|
|
5588
6192
|
return {
|
|
5589
6193
|
catalog,
|
|
5590
6194
|
loading,
|
|
@@ -5614,6 +6218,7 @@ window.__ModuleLoader__.load({
|
|
|
5614
6218
|
tab,
|
|
5615
6219
|
skillView,
|
|
5616
6220
|
sourceFilter,
|
|
6221
|
+
invocationFilter,
|
|
5617
6222
|
sortKey,
|
|
5618
6223
|
marketState,
|
|
5619
6224
|
marketCheck,
|
|
@@ -5644,6 +6249,8 @@ window.__ModuleLoader__.load({
|
|
|
5644
6249
|
subdividedProjects,
|
|
5645
6250
|
showLegend,
|
|
5646
6251
|
editMode,
|
|
6252
|
+
versionDialog,
|
|
6253
|
+
versionBusy,
|
|
5647
6254
|
actionNames,
|
|
5648
6255
|
viewNames,
|
|
5649
6256
|
normalized,
|
|
@@ -5655,6 +6262,7 @@ window.__ModuleLoader__.load({
|
|
|
5655
6262
|
sortKey,
|
|
5656
6263
|
uses
|
|
5657
6264
|
]),
|
|
6265
|
+
shortenedCount,
|
|
5658
6266
|
setLoadError,
|
|
5659
6267
|
setSuccessBanner,
|
|
5660
6268
|
setSearch,
|
|
@@ -5669,6 +6277,7 @@ window.__ModuleLoader__.load({
|
|
|
5669
6277
|
setTab,
|
|
5670
6278
|
setSkillView,
|
|
5671
6279
|
setSourceFilter,
|
|
6280
|
+
setInvocationFilter,
|
|
5672
6281
|
setSortKey,
|
|
5673
6282
|
setBranchChoice,
|
|
5674
6283
|
setMarketSyncDialog,
|
|
@@ -5686,6 +6295,7 @@ window.__ModuleLoader__.load({
|
|
|
5686
6295
|
setEditSearch,
|
|
5687
6296
|
setShowLegend,
|
|
5688
6297
|
setEditMode,
|
|
6298
|
+
setVersionDialog,
|
|
5689
6299
|
toggleGroupCollapse,
|
|
5690
6300
|
toggleSubdivide,
|
|
5691
6301
|
checkUpdate,
|
|
@@ -5702,6 +6312,15 @@ window.__ModuleLoader__.load({
|
|
|
5702
6312
|
requestDelete,
|
|
5703
6313
|
restoreTrash,
|
|
5704
6314
|
clearTrash,
|
|
6315
|
+
fixingPaths,
|
|
6316
|
+
fixDiagnostic,
|
|
6317
|
+
clearListFilters: (0, react.useCallback)(() => {
|
|
6318
|
+
setSearch("");
|
|
6319
|
+
setSourceFilter("all");
|
|
6320
|
+
setInvocationFilter("all");
|
|
6321
|
+
}, []),
|
|
6322
|
+
openVersionDialog,
|
|
6323
|
+
confirmVersionDialog,
|
|
5705
6324
|
requestDeleteSkill,
|
|
5706
6325
|
runDeleteSkill,
|
|
5707
6326
|
requestDeleteGroup,
|
|
@@ -5748,25 +6367,35 @@ window.__ModuleLoader__.load({
|
|
|
5748
6367
|
/** 工作区输入草稿:回车才应用,避免每次按键都触发目录重拉。 */
|
|
5749
6368
|
const [workspaceDraft, setWorkspaceDraft] = (0, react.useState)("");
|
|
5750
6369
|
const { catalog, loading, loadError, successBanner, updateState, detail, detailLoading, showForm, formName, formDesc, formRoot, formBusy, formMessage, hubConfig, tab, skillView, sourceFilter, sortKey, search, workspace, setWorkspace, sourcesState, tagBusy, busyNames, normalized, origins, sourceOptions, filtered, conflictDialog, confirmDialog, deleteSkillDialog, deleteGroupDialog, confirmClearTrash, branchChoice, branchBusy, marketSyncDialog, syncBusy, editingTag, editName, membersDraft, editSearch, uses, groupsState, sourceCheck, checkingSource, syncingSource, showLegend, editMode, setLoadError, setSuccessBanner, setDetail, setShowForm, setFormName, setFormDesc, setFormRoot, setFormMessage, setTab, setSkillView, setSourceFilter, setSortKey, setSearch, setConflictDialog, setConfirmDialog, setDeleteSkillDialog, setDeleteGroupDialog, setConfirmClearTrash, setBranchChoice, setMarketSyncDialog, setEditingTag, setEditName, setMembersDraft, setEditSearch, setShowLegend, setEditMode, checkUpdate, loadMarket, checkSources, requestSync, requestDelete, restoreTrash, clearTrash, runDeleteSkill, runDeleteGroup, runConfirmed, resolveConflict, confirmBranchChoice, confirmMarketSync, create, saveTag, deleteTag, enableDisabled } = hub;
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
6370
|
+
const { shortenedCount, fixingPaths, clearListFilters } = hub;
|
|
6371
|
+
if (detail !== null) {
|
|
6372
|
+
const disabledRecord = catalog?.disabled.find((record) => record.name === detail.name);
|
|
6373
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillDetailView, {
|
|
6374
|
+
detail,
|
|
6375
|
+
hubConfig,
|
|
6376
|
+
uses,
|
|
6377
|
+
groupsState,
|
|
6378
|
+
sourcesState,
|
|
6379
|
+
sourceCheck,
|
|
6380
|
+
checkingSource,
|
|
6381
|
+
syncingSource,
|
|
6382
|
+
loading: detailLoading,
|
|
6383
|
+
disabled: disabledRecord !== void 0,
|
|
6384
|
+
onEnable: disabledRecord !== void 0 ? () => {
|
|
6385
|
+
enableDisabled(disabledRecord).then(() => {
|
|
6386
|
+
setDetail(null);
|
|
6387
|
+
});
|
|
6388
|
+
} : void 0,
|
|
6389
|
+
onBack: () => {
|
|
6390
|
+
setDetail(null);
|
|
6391
|
+
},
|
|
6392
|
+
onCheck: (repo) => {
|
|
6393
|
+
checkSources(repo);
|
|
6394
|
+
},
|
|
6395
|
+
onSync: requestSync,
|
|
6396
|
+
onFollowDelete: requestDelete
|
|
6397
|
+
});
|
|
6398
|
+
}
|
|
5770
6399
|
if (editingTag !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TagEditorView, {
|
|
5771
6400
|
tag: editingTag,
|
|
5772
6401
|
editName,
|
|
@@ -5841,6 +6470,20 @@ window.__ModuleLoader__.load({
|
|
|
5841
6470
|
className: panel_module_css_default.hint,
|
|
5842
6471
|
children: tt("panel.incomplete")
|
|
5843
6472
|
}) : null,
|
|
6473
|
+
catalog !== null && (catalog.duplicateNames?.length ?? 0) > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
6474
|
+
type: "button",
|
|
6475
|
+
className: panel_module_css_default.opBtn,
|
|
6476
|
+
title: tt("row.duplicateHint"),
|
|
6477
|
+
onClick: () => {
|
|
6478
|
+
clearListFilters();
|
|
6479
|
+
},
|
|
6480
|
+
children: [
|
|
6481
|
+
"⚠ ",
|
|
6482
|
+
tt("row.duplicate"),
|
|
6483
|
+
"×",
|
|
6484
|
+
(catalog.duplicateNames ?? []).length
|
|
6485
|
+
]
|
|
6486
|
+
}) : null,
|
|
5844
6487
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5845
6488
|
className: panel_module_css_default.actions,
|
|
5846
6489
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -6131,6 +6774,27 @@ window.__ModuleLoader__.load({
|
|
|
6131
6774
|
})
|
|
6132
6775
|
]
|
|
6133
6776
|
}),
|
|
6777
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
6778
|
+
className: panel_module_css_default.select,
|
|
6779
|
+
value: hub.invocationFilter,
|
|
6780
|
+
onChange: (event) => {
|
|
6781
|
+
hub.setInvocationFilter(event.target.value);
|
|
6782
|
+
},
|
|
6783
|
+
children: [
|
|
6784
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
6785
|
+
value: "all",
|
|
6786
|
+
children: tt("filter.invocationAll")
|
|
6787
|
+
}),
|
|
6788
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
6789
|
+
value: "model",
|
|
6790
|
+
children: tt("filter.modelOnly")
|
|
6791
|
+
}),
|
|
6792
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
6793
|
+
value: "user",
|
|
6794
|
+
children: tt("filter.userOnly")
|
|
6795
|
+
})
|
|
6796
|
+
]
|
|
6797
|
+
}),
|
|
6134
6798
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6135
6799
|
className: panel_module_css_default.search,
|
|
6136
6800
|
value: search,
|
|
@@ -6148,6 +6812,23 @@ window.__ModuleLoader__.load({
|
|
|
6148
6812
|
})
|
|
6149
6813
|
]
|
|
6150
6814
|
}),
|
|
6815
|
+
catalog !== null && (filtered.length !== catalog.skills.length || hub.invocationFilter !== "all" || sourceFilter !== "all") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6816
|
+
className: panel_module_css_default.hintLine,
|
|
6817
|
+
style: {
|
|
6818
|
+
margin: "2px 2px 0",
|
|
6819
|
+
display: "flex",
|
|
6820
|
+
gap: 8,
|
|
6821
|
+
flexWrap: "wrap"
|
|
6822
|
+
},
|
|
6823
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tt("filter.showing", {
|
|
6824
|
+
shown: filtered.length,
|
|
6825
|
+
total: catalog.skills.length,
|
|
6826
|
+
filtered: hub.invocationFilter !== "all" || sourceFilter !== "all" ? tt("filter.filteredSuffix") : ""
|
|
6827
|
+
}) }), shortenedCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6828
|
+
title: tt("filter.shortened", { count: shortenedCount }),
|
|
6829
|
+
children: tt("filter.shortened", { count: shortenedCount })
|
|
6830
|
+
}) : null]
|
|
6831
|
+
}) : null,
|
|
6151
6832
|
filtered.length === 0 && search.trim() !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6152
6833
|
className: panel_module_css_default.empty,
|
|
6153
6834
|
children: tt("panel.empty")
|
|
@@ -6202,13 +6883,36 @@ window.__ModuleLoader__.load({
|
|
|
6202
6883
|
children: tt("panel.diagnostics")
|
|
6203
6884
|
}), catalog.diagnostics.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6204
6885
|
className: panel_module_css_default.diagRow,
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6886
|
+
style: {
|
|
6887
|
+
display: "flex",
|
|
6888
|
+
alignItems: "flex-start",
|
|
6889
|
+
gap: 10
|
|
6890
|
+
},
|
|
6891
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6892
|
+
style: {
|
|
6893
|
+
flex: 1,
|
|
6894
|
+
minWidth: 0
|
|
6895
|
+
},
|
|
6896
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6897
|
+
className: panel_module_css_default.diagPath,
|
|
6898
|
+
children: entry.path
|
|
6899
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6900
|
+
className: panel_module_css_default.diagReason,
|
|
6901
|
+
children: entry.reason
|
|
6902
|
+
})]
|
|
6903
|
+
}), entry.fixable === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6904
|
+
type: "button",
|
|
6905
|
+
className: panel_module_css_default.opBtn,
|
|
6906
|
+
disabled: fixingPaths.has(entry.path),
|
|
6907
|
+
onClick: () => {
|
|
6908
|
+
hub.fixDiagnostic(entry.path);
|
|
6909
|
+
},
|
|
6910
|
+
style: {
|
|
6911
|
+
flex: "none",
|
|
6912
|
+
alignSelf: "center"
|
|
6913
|
+
},
|
|
6914
|
+
children: fixingPaths.has(entry.path) ? tt("diag.fixing") : tt("diag.fix")
|
|
6915
|
+
}) : null]
|
|
6212
6916
|
}, entry.path))]
|
|
6213
6917
|
}) : null
|
|
6214
6918
|
] }) : null,
|
|
@@ -6255,6 +6959,28 @@ window.__ModuleLoader__.load({
|
|
|
6255
6959
|
confirmBranchChoice();
|
|
6256
6960
|
}
|
|
6257
6961
|
}) : null,
|
|
6962
|
+
hub.versionDialog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VersionChoiceDialog, {
|
|
6963
|
+
choice: hub.versionDialog,
|
|
6964
|
+
busy: hub.versionBusy,
|
|
6965
|
+
onSelect: (selected) => {
|
|
6966
|
+
hub.setVersionDialog({
|
|
6967
|
+
...hub.versionDialog,
|
|
6968
|
+
selected
|
|
6969
|
+
});
|
|
6970
|
+
},
|
|
6971
|
+
onCustom: (custom) => {
|
|
6972
|
+
hub.setVersionDialog({
|
|
6973
|
+
...hub.versionDialog,
|
|
6974
|
+
custom
|
|
6975
|
+
});
|
|
6976
|
+
},
|
|
6977
|
+
onCancel: () => {
|
|
6978
|
+
hub.setVersionDialog(null);
|
|
6979
|
+
},
|
|
6980
|
+
onConfirm: () => {
|
|
6981
|
+
hub.confirmVersionDialog();
|
|
6982
|
+
}
|
|
6983
|
+
}) : null,
|
|
6258
6984
|
marketSyncDialog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarketSyncDialog, {
|
|
6259
6985
|
dialog: marketSyncDialog,
|
|
6260
6986
|
busy: syncBusy,
|
|
@@ -6333,7 +7059,8 @@ window.__ModuleLoader__.load({
|
|
|
6333
7059
|
"locale",
|
|
6334
7060
|
"connection",
|
|
6335
7061
|
"remote",
|
|
6336
|
-
"settingsScope"
|
|
7062
|
+
"settingsScope",
|
|
7063
|
+
"inputTriggers"
|
|
6337
7064
|
];
|
|
6338
7065
|
/**
|
|
6339
7066
|
* Mount the settings card and the skill hub section.
|
|
@@ -6346,7 +7073,9 @@ window.__ModuleLoader__.load({
|
|
|
6346
7073
|
}), "dsh-skill-hub: dictionaries");
|
|
6347
7074
|
const t = ctx.locale.bind(NS);
|
|
6348
7075
|
const api = new SkillHubApi();
|
|
6349
|
-
const
|
|
7076
|
+
const scope = ctx.settingsScope.bind({ namespace: NS });
|
|
7077
|
+
const settingsCard = new SkillHubSettingsCardController(scope);
|
|
7078
|
+
ctx.effect(() => setupSkillSlashDots(ctx, api, scope), "dsh-skill-hub: slash dots");
|
|
6350
7079
|
ctx.effect(() => ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
|
|
6351
7080
|
name: "settings.plugin.item",
|
|
6352
7081
|
key: NS,
|