dsh-skill-hub 0.3.9 → 0.3.11

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.
Files changed (126) hide show
  1. package/CONTRIBUTING.md +31 -3
  2. package/README.md +1 -1
  3. package/README.zh.md +1 -1
  4. package/lib/client.js +1440 -1335
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +960 -724
  7. package/lib/types/client/api.d.ts +1 -5
  8. package/lib/types/client/locales/common.d.ts +1 -3
  9. package/lib/types/client/locales/market.d.ts +13 -5
  10. package/lib/types/client/locales/settings.d.ts +0 -2
  11. package/lib/types/client/locales/skills.d.ts +11 -13
  12. package/lib/types/client/locales.d.ts +25 -23
  13. package/lib/types/client/panel/CollectionCard.d.ts +52 -0
  14. package/lib/types/client/panel/GroupSummary.d.ts +13 -4
  15. package/lib/types/client/panel/GroupSwitchButton.d.ts +24 -0
  16. package/lib/types/client/panel/PanelDialogs.d.ts +49 -0
  17. package/lib/types/client/panel/ProjectTree.d.ts +31 -0
  18. package/lib/types/client/panel/ReorderButtons.d.ts +15 -0
  19. package/lib/types/client/panel/SkillDetailView.d.ts +0 -1
  20. package/lib/types/client/panel/SkillHubPanel.d.ts +5 -4
  21. package/lib/types/client/panel/SkillRow.d.ts +23 -5
  22. package/lib/types/client/panel/hooks/shared.d.ts +6 -0
  23. package/lib/types/client/panel/hooks/useCatalogFlow.d.ts +1 -1
  24. package/lib/types/client/panel/hooks/useGroupFlow.d.ts +1 -2
  25. package/lib/types/client/panel/hooks/useMarketFlow.d.ts +11 -10
  26. package/lib/types/client/panel/hooks/useRepoImportFlow.d.ts +38 -0
  27. package/lib/types/client/panel/hooks/useSourceFlow.d.ts +1 -1
  28. package/lib/types/client/panel/useDragReorder.d.ts +34 -0
  29. package/lib/types/client/panel/useSkillHub.d.ts +1 -3
  30. package/lib/types/client/settings-card.d.ts +29 -39
  31. package/lib/types/concurrency.d.ts +6 -0
  32. package/lib/types/error-text.d.ts +7 -0
  33. package/lib/types/protocol/stats.d.ts +33 -5
  34. package/lib/types/provider.d.ts +7 -0
  35. package/lib/types/repo/api.d.ts +36 -0
  36. package/lib/types/repo/discovery.d.ts +51 -0
  37. package/lib/types/repo/github-client.d.ts +0 -1
  38. package/lib/types/repo/install.d.ts +28 -0
  39. package/lib/types/repo/types.d.ts +22 -0
  40. package/lib/types/repo.d.ts +9 -120
  41. package/lib/types/routes/catalog-data.d.ts +31 -0
  42. package/lib/types/routes/collection.d.ts +13 -0
  43. package/lib/types/routes/deps.d.ts +70 -0
  44. package/lib/types/routes/helpers.d.ts +10 -110
  45. package/lib/types/routes/http.d.ts +39 -0
  46. package/lib/types/routes/route-state.d.ts +0 -2
  47. package/lib/types/skillfs/frontmatter.d.ts +20 -0
  48. package/lib/types/skillfs/paths.d.ts +7 -0
  49. package/lib/types/skillfs/scan.d.ts +40 -0
  50. package/lib/types/skillfs.d.ts +4 -64
  51. package/lib/types/stats/persistence.d.ts +48 -0
  52. package/lib/types/stats/scan.d.ts +52 -0
  53. package/lib/types/stats.d.ts +38 -42
  54. package/package.json +29 -28
  55. package/src/client/SkillHubSettingsCard.tsx +40 -98
  56. package/src/client/api.ts +0 -15
  57. package/src/client/locales/common.ts +2 -6
  58. package/src/client/locales/market.ts +25 -9
  59. package/src/client/locales/settings.ts +0 -4
  60. package/src/client/locales/skills.ts +22 -26
  61. package/src/client/panel/CollectionCard.tsx +125 -0
  62. package/src/client/panel/GroupSummary.tsx +16 -6
  63. package/src/client/panel/GroupSwitchButton.tsx +40 -0
  64. package/src/client/panel/MarketSourceRow.tsx +1 -1
  65. package/src/client/panel/PanelDialogs.tsx +161 -0
  66. package/src/client/panel/ProjectTree.tsx +106 -0
  67. package/src/client/panel/ReorderButtons.tsx +42 -0
  68. package/src/client/panel/RepoScanCard.tsx +21 -11
  69. package/src/client/panel/ScenesView.tsx +51 -26
  70. package/src/client/panel/SkillDetailView.tsx +1 -3
  71. package/src/client/panel/SkillHubPanel.tsx +99 -123
  72. package/src/client/panel/SkillRow.tsx +36 -17
  73. package/src/client/panel/SourcesView.tsx +103 -174
  74. package/src/client/panel/hooks/shared.ts +18 -0
  75. package/src/client/panel/hooks/useCatalogFlow.ts +14 -33
  76. package/src/client/panel/hooks/useGroupFlow.ts +8 -53
  77. package/src/client/panel/hooks/useMarketFlow.ts +26 -202
  78. package/src/client/panel/hooks/useMetaFlow.ts +2 -2
  79. package/src/client/panel/hooks/useRepoImportFlow.ts +197 -0
  80. package/src/client/panel/hooks/useSourceFlow.ts +11 -36
  81. package/src/client/panel/panel.module.css +121 -75
  82. package/src/client/panel/useDragReorder.ts +54 -0
  83. package/src/client/panel/useSkillHub.ts +12 -7
  84. package/src/client/settings-card.module.css +1 -1
  85. package/src/client/settings-card.tsx +52 -83
  86. package/src/concurrency.test.ts +33 -0
  87. package/src/concurrency.ts +19 -0
  88. package/src/error-text.test.ts +19 -0
  89. package/src/error-text.ts +10 -0
  90. package/src/index.ts +79 -18
  91. package/src/protocol/stats.ts +31 -5
  92. package/src/provider.test.ts +16 -0
  93. package/src/provider.ts +21 -2
  94. package/src/repo/api.ts +125 -0
  95. package/src/repo/discovery.ts +201 -0
  96. package/src/repo/github-client.ts +5 -10
  97. package/src/repo/install.ts +150 -0
  98. package/src/repo/types.ts +25 -0
  99. package/src/repo.ts +30 -503
  100. package/src/routes/catalog-data.ts +230 -0
  101. package/src/routes/catalog.ts +58 -47
  102. package/src/routes/collection.ts +36 -0
  103. package/src/routes/config.ts +2 -0
  104. package/src/routes/deps.ts +100 -0
  105. package/src/routes/groups.ts +15 -21
  106. package/src/routes/helpers.test.ts +57 -0
  107. package/src/routes/helpers.ts +43 -424
  108. package/src/routes/http.ts +119 -0
  109. package/src/routes/market.ts +10 -14
  110. package/src/routes/repo-import.ts +10 -12
  111. package/src/routes/route-state.test.ts +82 -0
  112. package/src/routes/route-state.ts +11 -6
  113. package/src/routes/sources.ts +18 -36
  114. package/src/routes.test.ts +72 -0
  115. package/src/skillfs/frontmatter.ts +179 -0
  116. package/src/skillfs/paths.ts +40 -0
  117. package/src/skillfs/scan.ts +175 -0
  118. package/src/skillfs.ts +11 -373
  119. package/src/stats/persistence.ts +85 -0
  120. package/src/stats/scan.ts +271 -0
  121. package/src/stats.test.ts +270 -6
  122. package/src/stats.ts +75 -207
  123. package/src/store/migrate.ts +25 -0
  124. package/src/store/store.ts +2 -0
  125. package/src/store.test.ts +29 -0
  126. package/src/update.ts +8 -42
package/lib/client.js CHANGED
@@ -206,10 +206,6 @@ window.__ModuleLoader__.load({
206
206
  config() {
207
207
  return this.get(SKILL_HUB_API.config);
208
208
  }
209
- /** Patch the hub's runtime config (null clears a saved override). */
210
- saveConfig(patch) {
211
- return this.post(SKILL_HUB_API.config, patch);
212
- }
213
209
  /** 用户 tag 分组 + 系统集合组 + origin 映射。 */
214
210
  groups() {
215
211
  return this.get(SKILL_HUB_API.groups);
@@ -233,10 +229,6 @@ window.__ModuleLoader__.load({
233
229
  async reorderTags(orderedIds) {
234
230
  return (await this.post(SKILL_HUB_API.tagReorder, { orderedIds })).tags;
235
231
  }
236
- /** 拖拽重排来源集合 */
237
- async reorderCollections(orderedNames) {
238
- return (await this.post(SKILL_HUB_API.collectionReorder, { orderedNames })).collections;
239
- }
240
232
  /** 拖拽重排来源顶层分组(project / collections / personal) */
241
233
  async reorderSourceGroups(orderedKeys) {
242
234
  return (await this.post(SKILL_HUB_API.sourceGroupReorder, { orderedKeys })).order;
@@ -284,8 +276,8 @@ window.__ModuleLoader__.load({
284
276
  //#region src/client/locales/common.ts
285
277
  /** 面板框架与通用:入口/视图切换/面板/图例/报错/更新检查/新建表单/诊断修复。从 locales.ts 按前缀拆出,键集合不变。 */
286
278
  const zhCommon = {
279
+ "drag.reorder": "拖拽调整顺序",
287
280
  "entry.label": "技能",
288
- "entry.tooltip": "技能中枢:浏览、分组与同步管理本地技能",
289
281
  "view.sources": "来源",
290
282
  "view.scenes": "场景",
291
283
  "view.market": "市场",
@@ -299,7 +291,6 @@ window.__ModuleLoader__.load({
299
291
  "legend.model": "模型可调 — Agent 可自动调用",
300
292
  "legend.user": "用户可调 — 仅显式点名时触发",
301
293
  "legend.hint": "每个技能最多一个圆点;无圆点 = 不可被调用。",
302
- "panel.enabled": "已启用",
303
294
  "panel.disabled": "已禁用",
304
295
  "panel.diagnostics": "发现诊断",
305
296
  "panel.empty": "没有匹配的技能。",
@@ -314,7 +305,6 @@ window.__ModuleLoader__.load({
314
305
  "update.upToDate": "已是最新版本 v{version}",
315
306
  "update.available": "发现新版本 v{version}(当前 v{current})",
316
307
  "update.newVersion": "新版本 v{version}",
317
- "update.viewRelease": "查看发布",
318
308
  "update.error": "检查更新失败:{error}",
319
309
  "update.unavailable": "暂无可用的发布版本",
320
310
  "diag.fix": "修复",
@@ -334,8 +324,8 @@ window.__ModuleLoader__.load({
334
324
  "err.dismiss": "知道了"
335
325
  };
336
326
  const enCommon = {
327
+ "drag.reorder": "Drag to reorder",
337
328
  "entry.label": "Skills",
338
- "entry.tooltip": "Skill hub: browse, group, and sync local skills",
339
329
  "view.sources": "Sources",
340
330
  "view.scenes": "Scenes",
341
331
  "view.market": "Market",
@@ -349,7 +339,6 @@ window.__ModuleLoader__.load({
349
339
  "legend.model": "Model-invocable — agents may call automatically",
350
340
  "legend.user": "User-invocable — explicit invocation only",
351
341
  "legend.hint": "At most one dot per skill; no dot = not invocable.",
352
- "panel.enabled": "Enabled",
353
342
  "panel.disabled": "Disabled",
354
343
  "panel.diagnostics": "Discovery diagnostics",
355
344
  "panel.empty": "No matching skills.",
@@ -364,7 +353,6 @@ window.__ModuleLoader__.load({
364
353
  "update.upToDate": "Up to date: v{version}",
365
354
  "update.available": "Update available: v{version} (current v{current})",
366
355
  "update.newVersion": "New version v{version}",
367
- "update.viewRelease": "View release",
368
356
  "update.error": "Update check failed: {error}",
369
357
  "update.unavailable": "No release available",
370
358
  "diag.fix": "Fix",
@@ -418,6 +406,7 @@ window.__ModuleLoader__.load({
418
406
  //#region src/client/locales/market.ts
419
407
  /** 市场与仓库扫描导入。从 locales.ts 按前缀拆出,键集合不变。 */
420
408
  const zhMarket = {
409
+ "market.syncHint": "将同步到上游最新版并提示可更新的本地技能",
421
410
  "market.addPlaceholder": "owner/repo 或 https://github.com/…",
422
411
  "market.addSource": "添加源",
423
412
  "market.noSources": "还没有市场源。从下方内置市场添加,或输入一个 GitHub 仓库。",
@@ -440,13 +429,11 @@ window.__ModuleLoader__.load({
440
429
  "market.deleteSource": "移除源",
441
430
  "market.scan": "扫描",
442
431
  "market.scanning": "扫描中…",
443
- "market.check": "检查",
444
432
  "market.checkUpdate": "检查更新",
445
433
  "market.checking": "检查中…",
446
434
  "market.upToDate": "已是最新",
447
435
  "market.update": "更新",
448
436
  "market.updateCount": "更新 {count} 个",
449
- "market.sync": "同步",
450
437
  "market.syncing": "同步中…",
451
438
  "market.updated": "有更新",
452
439
  "market.newRelease": "新版本 v{version}",
@@ -469,10 +456,19 @@ window.__ModuleLoader__.load({
469
456
  "market.syncNone": "该来源还没有导入本地技能。",
470
457
  "market.syncCancel": "跳过",
471
458
  "market.removeHint": "移除源不影响已安装的技能。",
459
+ "repo.scanTitle": "扫描结果",
460
+ "repo.close": "关闭",
461
+ "repo.searchPlaceholder": "搜索技能名…",
462
+ "repo.allRoots": "全部 {count}",
463
+ "repo.selection": "已选 {selected}/{total} · {size} · 显示 {shown}/{filtered}(过滤后)",
464
+ "repo.noMatch": "无匹配技能",
465
+ "repo.loadMore": "加载更多 {step} (剩余 {remaining})",
466
+ "repo.skillProgress": "{done}/{total} 个技能",
472
467
  "repo.ready": "发现 {count} 个可导入技能",
473
468
  "repo.truncated": "⚠ 仓库过大,仅展示部分技能(GitHub 截断),可缩小仓库或指定子目录",
474
469
  "repo.empty": "没有找到可导入技能。",
475
- "repo.selectAll": "全选",
470
+ "repo.selectVisible": "全选当前显示",
471
+ "repo.selectAllN": "全选全部 {count}",
476
472
  "repo.clearAll": "清空",
477
473
  "repo.import": "导入所选 ({count})",
478
474
  "repo.importing": "导入中…",
@@ -484,11 +480,12 @@ window.__ModuleLoader__.load({
484
480
  "repo.cancel": "取消",
485
481
  "repo.cancelled": "已取消 · 已导入 {imported}/{total} 已保留,临时文件已清理",
486
482
  "repo.importingCurrent": "正在导入: {name} ({done}/{total})",
487
- "repo.root.skills": "技能 skills",
488
- "repo.root.designTemplates": "模板 design-templates",
483
+ "repo.root.skills": "skills",
484
+ "repo.root.designTemplates": "design-templates",
489
485
  "repo.root.generic": "{root}"
490
486
  };
491
487
  const enMarket = {
488
+ "market.syncHint": "Sync to the latest upstream and report local skills that can update",
492
489
  "market.addPlaceholder": "owner/repo or https://github.com/…",
493
490
  "market.addSource": "Add source",
494
491
  "market.noSources": "No market sources yet. Add one from the built-in catalog below, or enter a GitHub repo.",
@@ -511,13 +508,11 @@ window.__ModuleLoader__.load({
511
508
  "market.deleteSource": "Remove source",
512
509
  "market.scan": "Scan",
513
510
  "market.scanning": "Scanning…",
514
- "market.check": "Check",
515
511
  "market.checkUpdate": "Check for updates",
516
512
  "market.checking": "Checking…",
517
513
  "market.upToDate": "Up to date",
518
514
  "market.update": "Update",
519
515
  "market.updateCount": "Update {count}",
520
- "market.sync": "Sync",
521
516
  "market.syncing": "Syncing…",
522
517
  "market.updated": "Updates available",
523
518
  "market.newRelease": "New release v{version}",
@@ -540,10 +535,19 @@ window.__ModuleLoader__.load({
540
535
  "market.syncNone": "No local skills tracked from this repo yet.",
541
536
  "market.syncCancel": "Skip",
542
537
  "market.removeHint": "Removing a source does not affect installed skills.",
538
+ "repo.scanTitle": "Scan result",
539
+ "repo.close": "Close",
540
+ "repo.searchPlaceholder": "Search skill name…",
541
+ "repo.allRoots": "All {count}",
542
+ "repo.selection": "Selected {selected}/{total} · {size} · showing {shown}/{filtered} (filtered)",
543
+ "repo.noMatch": "No matching skills",
544
+ "repo.loadMore": "Load {step} more ({remaining} left)",
545
+ "repo.skillProgress": "{done}/{total} skills",
543
546
  "repo.ready": "{count} importable skills found",
544
547
  "repo.truncated": "⚠ Repo too large, only partial skills shown (GitHub truncated)",
545
548
  "repo.empty": "No importable skills found.",
546
- "repo.selectAll": "Select all",
549
+ "repo.selectVisible": "Select visible",
550
+ "repo.selectAllN": "Select all {count}",
547
551
  "repo.clearAll": "Clear",
548
552
  "repo.import": "Import selected ({count})",
549
553
  "repo.importing": "Importing…",
@@ -556,7 +560,7 @@ window.__ModuleLoader__.load({
556
560
  "repo.cancelled": "Cancelled · {imported}/{total} kept, temp files cleaned",
557
561
  "repo.importingCurrent": "Importing: {name} ({done}/{total})",
558
562
  "repo.root.skills": "Skills",
559
- "repo.root.designTemplates": "Templates design-templates",
563
+ "repo.root.designTemplates": "Templates",
560
564
  "repo.root.generic": "{root}"
561
565
  };
562
566
  //#endregion
@@ -587,8 +591,6 @@ window.__ModuleLoader__.load({
587
591
  "settings.githubTokenHint": "市场/来源走 GitHub API,匿名每小时 60 次,填 token 后 5000 次。细粒度 token 无需任何权限。明文存本地设置,仅回环可读;留空即匿名(或跟随 GITHUB_TOKEN 环境变量)。",
588
592
  "settings.githubTokenPlaceholder": "ghp_… / github_pat_…(留空为匿名)",
589
593
  "settings.inherit": "继承",
590
- "settings.on": "开",
591
- "settings.off": "关",
592
594
  "settings.overridden": "已覆盖",
593
595
  "settings.reset": "恢复默认",
594
596
  "settings.unsaved": "未保存",
@@ -626,8 +628,6 @@ window.__ModuleLoader__.load({
626
628
  "settings.githubTokenHint": "Market/sources use the GitHub API: 60 req/h anonymous, 5000/hr with a token. A fine-grained token needs no permissions. Stored plaintext in local settings, loopback-only; empty means anonymous (or GITHUB_TOKEN env).",
627
629
  "settings.githubTokenPlaceholder": "ghp_… / github_pat_… (empty = anonymous)",
628
630
  "settings.inherit": "Inherit",
629
- "settings.on": "On",
630
- "settings.off": "Off",
631
631
  "settings.overridden": "Overridden",
632
632
  "settings.reset": "Reset to default",
633
633
  "settings.unsaved": "Unsaved",
@@ -644,15 +644,14 @@ window.__ModuleLoader__.load({
644
644
  //#region src/client/locales/skills.ts
645
645
  /** 技能浏览:行徽章/分组/场景/过滤排序/相对时间。从 locales.ts 按前缀拆出,键集合不变。 */
646
646
  const zhSkills = {
647
- "group.project-dsh": "项目 .dsh/skills",
648
- "group.project-agents": "项目 .agents/skills",
649
- "group.custom": "自定义目录",
650
- "group.runtime": "运行时",
651
- "group.user-dsh": "用户 ~/.dsh/skills",
652
- "group.user-agents": "用户 ~/.agents/skills",
653
- "group.bundled": "内置",
654
- "group.other": "其他来源",
647
+ "edit.start": "编辑",
648
+ "edit.hint": "开启后显示删除与排序操作",
649
+ "edit.done": "完成",
655
650
  "groups.new": "新建场景",
651
+ "groups.collapseAll": "全部折叠",
652
+ "groups.expandAll": "全部展开",
653
+ "reorder.up": "上移",
654
+ "reorder.down": "下移",
656
655
  "groups.namePlaceholder": "场景名",
657
656
  "groups.edit": "编辑",
658
657
  "groups.rename": "保存名称",
@@ -671,6 +670,10 @@ window.__ModuleLoader__.load({
671
670
  "groups.conflictTitle": "关闭此组?",
672
671
  "groups.conflictText": "以下技能也在其他组中开启,关闭它们会影响那些组:",
673
672
  "filter.allSources": "全部来源",
673
+ "filter.title": "筛选",
674
+ "filter.source": "来源",
675
+ "filter.invocation": "调用方式",
676
+ "filter.clear": "清除筛选",
674
677
  "filter.private": "个人",
675
678
  "sort.name": "按名称",
676
679
  "sort.added": "按添加时间",
@@ -694,11 +697,6 @@ window.__ModuleLoader__.load({
694
697
  "row.open": "查看 {name} 详情(回车)",
695
698
  "row.duplicate": "重名",
696
699
  "row.duplicateHint": "同名技能存在多个来源,当前显示为优先级最高的版本",
697
- "row.copy": "复制",
698
- "row.copyMention": "复制 $mention",
699
- "row.copied": "已复制",
700
- "row.copyPath": "复制路径",
701
- "filter.invocation": "调用",
702
700
  "filter.invocationAll": "全部调用",
703
701
  "filter.modelOnly": "模型可调",
704
702
  "filter.userOnly": "用户可调",
@@ -707,15 +705,14 @@ window.__ModuleLoader__.load({
707
705
  "filter.shortened": "短描述 {count} 条,悬浮看全文"
708
706
  };
709
707
  const enSkills = {
710
- "group.project-dsh": "Project .dsh/skills",
711
- "group.project-agents": "Project .agents/skills",
712
- "group.custom": "Custom directories",
713
- "group.runtime": "Runtime",
714
- "group.user-dsh": "User ~/.dsh/skills",
715
- "group.user-agents": "User ~/.agents/skills",
716
- "group.bundled": "Bundled",
717
- "group.other": "Other sources",
708
+ "edit.start": "Edit",
709
+ "edit.hint": "Show delete and reorder actions",
710
+ "edit.done": "Done",
718
711
  "groups.new": "New scene",
712
+ "groups.collapseAll": "Collapse all",
713
+ "groups.expandAll": "Expand all",
714
+ "reorder.up": "Move up",
715
+ "reorder.down": "Move down",
719
716
  "groups.namePlaceholder": "Scene name",
720
717
  "groups.edit": "Edit",
721
718
  "groups.rename": "Save name",
@@ -734,6 +731,10 @@ window.__ModuleLoader__.load({
734
731
  "groups.conflictTitle": "Close this group?",
735
732
  "groups.conflictText": "These skills are also enabled in other groups; closing them affects those groups:",
736
733
  "filter.allSources": "All sources",
734
+ "filter.title": "Filter",
735
+ "filter.source": "Source",
736
+ "filter.invocation": "Invocation",
737
+ "filter.clear": "Clear filters",
737
738
  "filter.private": "Personal",
738
739
  "sort.name": "By name",
739
740
  "sort.added": "By added",
@@ -757,11 +758,6 @@ window.__ModuleLoader__.load({
757
758
  "row.open": "View {name} details (Enter)",
758
759
  "row.duplicate": "duplicate",
759
760
  "row.duplicateHint": "Multiple sources provide this name; the highest-priority one is shown",
760
- "row.copy": "Copy",
761
- "row.copyMention": "Copy $mention",
762
- "row.copied": "Copied",
763
- "row.copyPath": "Copy path",
764
- "filter.invocation": "Invocation",
765
761
  "filter.invocationAll": "All",
766
762
  "filter.modelOnly": "Model",
767
763
  "filter.userOnly": "User",
@@ -1331,7 +1327,7 @@ window.__ModuleLoader__.load({
1331
1327
  }
1332
1328
  //#endregion
1333
1329
  //#region \0dsh-css:/Users/huanyi/Documents/personal/tools/dsh-skill-hub/src/client/settings-card.module.css.mjs
1334
- const css$1 = ".TqD_hG_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.TqD_hG_card:hover{border-color:var(--dsw-alias-label-dimmed)}.TqD_hG_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.TqD_hG_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.TqD_hG_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.TqD_hG_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.TqD_hG_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.TqD_hG_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.TqD_hG_versionTag{opacity:.45;white-space:nowrap;font-variant-numeric:tabular-nums;vertical-align:1px;margin-left:6px;font-size:12px;font-weight:500}.TqD_hG_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.TqD_hG_chevron,.TqD_hG_chevronOpen{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.TqD_hG_chevronOpen{transform:rotate(180deg)}.TqD_hG_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.TqD_hG_readOnly,.TqD_hG_notExposed{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.TqD_hG_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.TqD_hG_failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}.TqD_hG_discard,.TqD_hG_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.TqD_hG_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.TqD_hG_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.TqD_hG_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.TqD_hG_discard:disabled,.TqD_hG_save:disabled{opacity:.4;cursor:default}.TqD_hG_discard:focus-visible,.TqD_hG_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.TqD_hG_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.TqD_hG_field+.TqD_hG_field{border-top:1px solid var(--dsw-alias-border-l2)}.TqD_hG_head{align-items:center;gap:8px;display:flex}.TqD_hG_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.TqD_hG_badges{align-items:center;gap:8px;display:inline-flex}.TqD_hG_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.TqD_hG_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.TqD_hG_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.TqD_hG_reset:disabled{cursor:default}.TqD_hG_input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.TqD_hG_input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.TqD_hG_input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.TqD_hG_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}.TqD_hG_colorRow{align-items:center;gap:8px;display:flex}.TqD_hG_colorInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);cursor:pointer;border-radius:8px;width:34px;height:34px;padding:0}.TqD_hG_colorInput:disabled{opacity:.5;cursor:default}.TqD_hG_colorInput::-webkit-color-swatch-wrapper{padding:2px}.TqD_hG_colorInput::-webkit-color-swatch{border:none;border-radius:4px}.TqD_hG_colorText{flex:1;min-width:0}.TqD_hG_switchRow{align-items:center;gap:12px;display:flex}.TqD_hG_switchText{flex:1;min-width:0}.TqD_hG_switch,.TqD_hG_switchOn{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);cursor:pointer;border-radius:999px;flex:none;width:38px;height:22px;padding:2px;transition:background .15s,border-color .15s}.TqD_hG_switchOn{border-color:var(--dsw-alias-state-business-primary,#2f81f7);background:var(--dsw-alias-state-business-primary,#2f81f7)}.TqD_hG_switch:disabled,.TqD_hG_switchOn:disabled{opacity:.4;cursor:default}.TqD_hG_switchThumb{background:#fff;border-radius:50%;width:16px;height:16px;transition:transform .15s;display:block;transform:translate(0);box-shadow:0 1px 2px #00000040}.TqD_hG_switchOn .TqD_hG_switchThumb{transform:translate(16px)}";
1330
+ const css$1 = ".TqD_hG_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.TqD_hG_card:hover{border-color:var(--dsw-alias-label-dimmed)}.TqD_hG_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.TqD_hG_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.TqD_hG_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.TqD_hG_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.TqD_hG_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.TqD_hG_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.TqD_hG_versionTag{opacity:.45;white-space:nowrap;font-variant-numeric:tabular-nums;vertical-align:1px;margin-left:6px;font-size:12px;font-weight:500}.TqD_hG_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.TqD_hG_chevron,.TqD_hG_chevronOpen{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.TqD_hG_chevronOpen{transform:rotate(180deg)}.TqD_hG_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.TqD_hG_readOnly,.TqD_hG_notExposed{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.TqD_hG_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.TqD_hG_failed{min-width:0;color:var(--dsw-alias-state-error-primary,#d1242f);flex:1;margin:0;font-size:12px;line-height:1.5}.TqD_hG_discard,.TqD_hG_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.TqD_hG_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.TqD_hG_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.TqD_hG_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.TqD_hG_discard:disabled,.TqD_hG_save:disabled{opacity:.4;cursor:default}.TqD_hG_discard:focus-visible,.TqD_hG_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.TqD_hG_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.TqD_hG_field+.TqD_hG_field{border-top:1px solid var(--dsw-alias-border-l2)}.TqD_hG_head{align-items:center;gap:8px;display:flex}.TqD_hG_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.TqD_hG_badges{align-items:center;gap:8px;display:inline-flex}.TqD_hG_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.TqD_hG_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.TqD_hG_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.TqD_hG_reset:disabled{cursor:default}.TqD_hG_input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.TqD_hG_input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.TqD_hG_input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.TqD_hG_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}.TqD_hG_colorRow{align-items:center;gap:8px;display:flex}.TqD_hG_colorInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);cursor:pointer;border-radius:8px;width:34px;height:34px;padding:0}.TqD_hG_colorInput:disabled{opacity:.5;cursor:default}.TqD_hG_colorInput::-webkit-color-swatch-wrapper{padding:2px}.TqD_hG_colorInput::-webkit-color-swatch{border:none;border-radius:4px}.TqD_hG_colorText{flex:1;min-width:0}.TqD_hG_switchRow{align-items:center;gap:12px;display:flex}.TqD_hG_switchText{flex:1;min-width:0}.TqD_hG_switch,.TqD_hG_switchOn{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);cursor:pointer;border-radius:999px;flex:none;width:38px;height:22px;padding:2px;transition:background .15s,border-color .15s}.TqD_hG_switchOn{border-color:var(--dsw-alias-state-business-primary,#2f81f7);background:var(--dsw-alias-state-business-primary,#2f81f7)}.TqD_hG_switch:disabled,.TqD_hG_switchOn:disabled{opacity:.4;cursor:default}.TqD_hG_switchThumb{background:#fff;border-radius:50%;width:16px;height:16px;transition:transform .15s;display:block;transform:translate(0);box-shadow:0 1px 2px #00000040}.TqD_hG_switchOn .TqD_hG_switchThumb{transform:translate(16px)}";
1335
1331
  const tagId$1 = "dsh-skill-hub/settings-card.module.css";
1336
1332
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
1337
1333
  const tag = document.createElement("style");
@@ -1474,6 +1470,35 @@ window.__ModuleLoader__.load({
1474
1470
  }) : null]
1475
1471
  });
1476
1472
  }
1473
+ /**
1474
+ * The head row every staged field shares: the label (plain text when the
1475
+ * control has no id) plus the overridden badge and its reset button.
1476
+ */
1477
+ function FieldShell(props) {
1478
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1479
+ className: settings_card_module_css_default.head,
1480
+ children: [props.id !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1481
+ className: settings_card_module_css_default.label,
1482
+ htmlFor: props.id,
1483
+ children: props.label
1484
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1485
+ className: settings_card_module_css_default.label,
1486
+ children: props.label
1487
+ }), props.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1488
+ className: settings_card_module_css_default.badges,
1489
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1490
+ className: settings_card_module_css_default.badge,
1491
+ children: props.overriddenLabel
1492
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1493
+ type: "button",
1494
+ className: settings_card_module_css_default.reset,
1495
+ disabled: props.disabled,
1496
+ onClick: props.onReset,
1497
+ children: props.resetLabel
1498
+ })]
1499
+ }) : null]
1500
+ });
1501
+ }
1477
1502
  function SwitchField(props) {
1478
1503
  const checked = props.text !== "false";
1479
1504
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -1482,25 +1507,7 @@ window.__ModuleLoader__.load({
1482
1507
  className: settings_card_module_css_default.switchRow,
1483
1508
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1484
1509
  className: settings_card_module_css_default.switchText,
1485
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1486
- className: settings_card_module_css_default.head,
1487
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1488
- className: settings_card_module_css_default.label,
1489
- children: props.label
1490
- }), props.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1491
- className: settings_card_module_css_default.badges,
1492
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1493
- className: settings_card_module_css_default.badge,
1494
- children: props.overriddenLabel
1495
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1496
- type: "button",
1497
- className: settings_card_module_css_default.reset,
1498
- disabled: props.disabled,
1499
- onClick: props.onReset,
1500
- children: props.resetLabel
1501
- })]
1502
- }) : null]
1503
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1510
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldShell, { ...props }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1504
1511
  className: settings_card_module_css_default.hint,
1505
1512
  children: props.hint
1506
1513
  })]
@@ -1524,26 +1531,7 @@ window.__ModuleLoader__.load({
1524
1531
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1525
1532
  className: settings_card_module_css_default.field,
1526
1533
  children: [
1527
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1528
- className: settings_card_module_css_default.head,
1529
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1530
- className: settings_card_module_css_default.label,
1531
- htmlFor: props.id,
1532
- children: props.label
1533
- }), props.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1534
- className: settings_card_module_css_default.badges,
1535
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1536
- className: settings_card_module_css_default.badge,
1537
- children: props.overriddenLabel
1538
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1539
- type: "button",
1540
- className: settings_card_module_css_default.reset,
1541
- disabled: props.disabled,
1542
- onClick: props.onReset,
1543
- children: props.resetLabel
1544
- })]
1545
- }) : null]
1546
- }),
1534
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldShell, { ...props }),
1547
1535
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1548
1536
  className: settings_card_module_css_default.colorRow,
1549
1537
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
@@ -1577,26 +1565,7 @@ window.__ModuleLoader__.load({
1577
1565
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1578
1566
  className: settings_card_module_css_default.field,
1579
1567
  children: [
1580
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1581
- className: settings_card_module_css_default.head,
1582
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1583
- className: settings_card_module_css_default.label,
1584
- htmlFor: props.id,
1585
- children: props.label
1586
- }), props.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1587
- className: settings_card_module_css_default.badges,
1588
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1589
- className: settings_card_module_css_default.badge,
1590
- children: props.overriddenLabel
1591
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1592
- type: "button",
1593
- className: settings_card_module_css_default.reset,
1594
- disabled: props.disabled,
1595
- onClick: props.onReset,
1596
- children: props.resetLabel
1597
- })]
1598
- }) : null]
1599
- }),
1568
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldShell, { ...props }),
1600
1569
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1601
1570
  id: props.id,
1602
1571
  type: "password",
@@ -1622,26 +1591,7 @@ window.__ModuleLoader__.load({
1622
1591
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1623
1592
  className: settings_card_module_css_default.field,
1624
1593
  children: [
1625
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1626
- className: settings_card_module_css_default.head,
1627
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1628
- className: settings_card_module_css_default.label,
1629
- htmlFor: props.id,
1630
- children: props.label
1631
- }), props.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1632
- className: settings_card_module_css_default.badges,
1633
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1634
- className: settings_card_module_css_default.badge,
1635
- children: props.overriddenLabel
1636
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1637
- type: "button",
1638
- className: settings_card_module_css_default.reset,
1639
- disabled: props.disabled,
1640
- onClick: props.onReset,
1641
- children: props.resetLabel
1642
- })]
1643
- }) : null]
1644
- }),
1594
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldShell, { ...props }),
1645
1595
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1646
1596
  id: props.id,
1647
1597
  type: "text",
@@ -2669,6 +2619,77 @@ window.__ModuleLoader__.load({
2669
2619
  };
2670
2620
  }
2671
2621
  };
2622
+ /** The card's field rows in render order; the rendered DOM order follows it. */
2623
+ const FIELD_ROWS = [
2624
+ {
2625
+ kind: "switch",
2626
+ field: "enabled",
2627
+ label: "settings.enabled",
2628
+ hint: "settings.enabledHint"
2629
+ },
2630
+ {
2631
+ kind: "switch",
2632
+ field: "announceToAgent",
2633
+ label: "settings.announceToAgent",
2634
+ hint: "settings.announceToAgentHint"
2635
+ },
2636
+ {
2637
+ kind: "color",
2638
+ field: "dotModelColor",
2639
+ id: "skill-hub-dot-model-color",
2640
+ label: "settings.dotModelColor",
2641
+ hint: "settings.dotModelColorHint",
2642
+ defaultColor: DEFAULT_DOT_MODEL_COLOR
2643
+ },
2644
+ {
2645
+ kind: "color",
2646
+ field: "dotUserColor",
2647
+ id: "skill-hub-dot-user-color",
2648
+ label: "settings.dotUserColor",
2649
+ hint: "settings.dotUserColorHint",
2650
+ defaultColor: DEFAULT_DOT_USER_COLOR
2651
+ },
2652
+ {
2653
+ kind: "switch",
2654
+ field: "showUseCount",
2655
+ label: "settings.showUseCount",
2656
+ hint: "settings.showUseCountHint"
2657
+ },
2658
+ {
2659
+ kind: "switch",
2660
+ field: "showUseTime",
2661
+ label: "settings.showUseTime",
2662
+ hint: "settings.showUseTimeHint"
2663
+ },
2664
+ {
2665
+ kind: "switch",
2666
+ field: "showGroupSummary",
2667
+ label: "settings.showGroupSummary",
2668
+ hint: "settings.showGroupSummaryHint"
2669
+ },
2670
+ {
2671
+ kind: "number",
2672
+ field: "statsWindowDays",
2673
+ id: "skill-hub-stats-window-days",
2674
+ label: "settings.statsWindowDays",
2675
+ hint: "settings.statsWindowDaysHint"
2676
+ },
2677
+ {
2678
+ kind: "number",
2679
+ field: "statsScanMinutes",
2680
+ id: "skill-hub-stats-scan-minutes",
2681
+ label: "settings.statsScanMinutes",
2682
+ hint: "settings.statsScanMinutesHint"
2683
+ },
2684
+ {
2685
+ kind: "secret",
2686
+ field: "githubToken",
2687
+ id: "skill-hub-github-token",
2688
+ label: "settings.githubToken",
2689
+ hint: "settings.githubTokenHint",
2690
+ placeholder: "settings.githubTokenPlaceholder"
2691
+ }
2692
+ ];
2672
2693
  /**
2673
2694
  * Render the dsh-skill-hub card.
2674
2695
  * @param props - locale copy, the card snapshot, and its form actions.
@@ -2693,7 +2714,7 @@ window.__ModuleLoader__.load({
2693
2714
  resetLabel: t("settings.reset"),
2694
2715
  disabled
2695
2716
  };
2696
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(PluginSettingsCard, {
2717
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PluginSettingsCard, {
2697
2718
  t,
2698
2719
  titleKey: "settings.title",
2699
2720
  descriptionKey: "settings.description",
@@ -2701,151 +2722,43 @@ window.__ModuleLoader__.load({
2701
2722
  state,
2702
2723
  onSave: props.save,
2703
2724
  onDiscard: props.discard,
2704
- children: [
2705
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SwitchField, {
2706
- label: t("settings.enabled"),
2707
- hint: t("settings.enabledHint"),
2708
- ...fieldProps,
2709
- ...state.enabled,
2710
- onEdit: (text) => {
2711
- props.edit("enabled", text);
2712
- },
2713
- onReset: () => {
2714
- props.resetField("enabled");
2715
- }
2716
- }),
2717
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SwitchField, {
2718
- label: t("settings.announceToAgent"),
2719
- hint: t("settings.announceToAgentHint"),
2720
- ...fieldProps,
2721
- ...state.announceToAgent,
2722
- onEdit: (text) => {
2723
- props.edit("announceToAgent", text);
2724
- },
2725
- onReset: () => {
2726
- props.resetField("announceToAgent");
2727
- }
2728
- }),
2729
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ColorField, {
2730
- id: "skill-hub-dot-model-color",
2731
- label: t("settings.dotModelColor"),
2732
- hint: t("settings.dotModelColorHint"),
2733
- inheritLabel: t("settings.inherit"),
2734
- defaultColor: DEFAULT_DOT_MODEL_COLOR,
2735
- ...fieldProps,
2736
- ...state.dotModelColor,
2737
- onEdit: (text) => {
2738
- props.edit("dotModelColor", text);
2739
- },
2740
- onReset: () => {
2741
- props.resetField("dotModelColor");
2742
- }
2743
- }),
2744
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ColorField, {
2745
- id: "skill-hub-dot-user-color",
2746
- label: t("settings.dotUserColor"),
2747
- hint: t("settings.dotUserColorHint"),
2748
- inheritLabel: t("settings.inherit"),
2749
- defaultColor: DEFAULT_DOT_USER_COLOR,
2750
- ...fieldProps,
2751
- ...state.dotUserColor,
2752
- onEdit: (text) => {
2753
- props.edit("dotUserColor", text);
2754
- },
2755
- onReset: () => {
2756
- props.resetField("dotUserColor");
2757
- }
2758
- }),
2759
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SwitchField, {
2760
- label: t("settings.showUseCount"),
2761
- hint: t("settings.showUseCountHint"),
2762
- ...fieldProps,
2763
- ...state.showUseCount,
2764
- onEdit: (text) => {
2765
- props.edit("showUseCount", text);
2766
- },
2767
- onReset: () => {
2768
- props.resetField("showUseCount");
2769
- }
2770
- }),
2771
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SwitchField, {
2772
- label: t("settings.showUseTime"),
2773
- hint: t("settings.showUseTimeHint"),
2774
- ...fieldProps,
2775
- ...state.showUseTime,
2776
- onEdit: (text) => {
2777
- props.edit("showUseTime", text);
2778
- },
2779
- onReset: () => {
2780
- props.resetField("showUseTime");
2781
- }
2782
- }),
2783
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SwitchField, {
2784
- label: t("settings.showGroupSummary"),
2785
- hint: t("settings.showGroupSummaryHint"),
2786
- ...fieldProps,
2787
- ...state.showGroupSummary,
2788
- onEdit: (text) => {
2789
- props.edit("showGroupSummary", text);
2790
- },
2791
- onReset: () => {
2792
- props.resetField("showGroupSummary");
2793
- }
2794
- }),
2795
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberField, {
2796
- id: "skill-hub-stats-window-days",
2797
- label: t("settings.statsWindowDays"),
2798
- hint: t("settings.statsWindowDaysHint"),
2799
- inheritLabel: t("settings.inherit"),
2725
+ children: FIELD_ROWS.map((row) => {
2726
+ const value = state[row.field];
2727
+ const common = {
2728
+ label: t(row.label),
2729
+ hint: t(row.hint),
2800
2730
  ...fieldProps,
2801
- text: state.statsWindowDays.text,
2802
- overridden: state.statsWindowDays.overridden,
2803
- invalid: state.statsWindowDays.invalid,
2731
+ ...value,
2804
2732
  onEdit: (text) => {
2805
- props.edit("statsWindowDays", text);
2733
+ props.edit(row.field, text);
2806
2734
  },
2807
2735
  onReset: () => {
2808
- props.resetField("statsWindowDays");
2736
+ props.resetField(row.field);
2809
2737
  }
2810
- }),
2811
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberField, {
2812
- id: "skill-hub-stats-scan-minutes",
2813
- label: t("settings.statsScanMinutes"),
2814
- hint: t("settings.statsScanMinutesHint"),
2738
+ };
2739
+ if (row.kind === "switch") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SwitchField, { ...common }, row.field);
2740
+ if (row.kind === "color") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ColorField, {
2741
+ ...common,
2742
+ id: row.id,
2815
2743
  inheritLabel: t("settings.inherit"),
2816
- ...fieldProps,
2817
- text: state.statsScanMinutes.text,
2818
- overridden: state.statsScanMinutes.overridden,
2819
- invalid: state.statsScanMinutes.invalid,
2820
- onEdit: (text) => {
2821
- props.edit("statsScanMinutes", text);
2822
- },
2823
- onReset: () => {
2824
- props.resetField("statsScanMinutes");
2825
- }
2826
- }),
2827
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SecretField, {
2828
- id: "skill-hub-github-token",
2829
- label: t("settings.githubToken"),
2830
- hint: t("settings.githubTokenHint"),
2831
- placeholder: t("settings.githubTokenPlaceholder"),
2832
- ...fieldProps,
2833
- text: state.githubToken.text,
2834
- overridden: state.githubToken.overridden,
2835
- invalid: state.githubToken.invalid,
2836
- onEdit: (text) => {
2837
- props.edit("githubToken", text);
2838
- },
2839
- onReset: () => {
2840
- props.resetField("githubToken");
2841
- }
2842
- })
2843
- ]
2744
+ defaultColor: row.defaultColor
2745
+ }, row.field);
2746
+ if (row.kind === "number") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberField, {
2747
+ ...common,
2748
+ id: row.id,
2749
+ inheritLabel: t("settings.inherit")
2750
+ }, row.field);
2751
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SecretField, {
2752
+ ...common,
2753
+ id: row.id,
2754
+ placeholder: t(row.placeholder)
2755
+ }, row.field);
2756
+ })
2844
2757
  });
2845
2758
  }
2846
2759
  //#endregion
2847
2760
  //#region \0dsh-css:/Users/huanyi/Documents/personal/tools/dsh-skill-hub/src/client/panel/panel.module.css.mjs
2848
- 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}";
2761
+ const css = "._6VtqdG_panel{-webkit-font-smoothing:antialiased;color-scheme:light dark;--hub-model:#2f81f7;--hub-user:#3fb950;--hub-accent:var(--dsw-alias-state-business-primary,#2f81f7);--hub-text:var(--dsw-alias-label-primary,currentColor);--hub-text-2:var(--dsw-alias-label-secondary,#61666b);--hub-text-3:var(--dsw-alias-label-tertiary,#81858c);--hub-text-4:var(--dsw-alias-label-caption,#adb2b8);--hub-surface:var(--dsw-alias-bg-module-platform,#8080800f);--hub-hover:var(--dsw-alias-interactive-bg-hover,#80808014);--hub-active:var(--dsw-alias-interactive-bg-active,#80808029);--hub-border:var(--dsw-alias-border-l2,#80808021);--hub-border-2:var(--dsw-alias-border-l3,#80808033);--hub-danger:var(--dsw-alias-state-error-primary,#d1242f);--hub-success:var(--dsw-alias-state-success-primary,#3fb950);--hub-warn:var(--dsw-alias-state-warn-primary,#d29922);--hub-on-accent:var(--dsw-alias-label-primary-foreground,#fff);--hub-skeleton:var(--dsw-alias-bg-skeleton,#8080801a);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{color:var(--hub-text-3);font-size:12px}._6VtqdG_actions{gap:8px;margin-left:auto;display:flex}._6VtqdG_segmented{background:var(--hub-hover);border-radius:9px;gap:2px;padding:2px;display:flex}._6VtqdG_segBtn{color:var(--hub-text-2);cursor:pointer;background:0 0;border:none;border-radius:7px;padding:5px 13px;font-size:12px;transition:background .15s,opacity .15s}._6VtqdG_segBtn:hover{color:var(--hub-text)}._6VtqdG_segBtnActive{background:var(--hub-active);opacity:1;font-weight:600}._6VtqdG_search{background:var(--hub-surface);width:100%;color:inherit;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:var(--hub-active)}._6VtqdG_section{background:var(--hub-surface);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{color:var(--hub-text);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{background:var(--hub-hover);color:inherit;cursor:pointer;border:none;border-radius:999px;padding:3px 11px;font-size:11px;transition:background .15s}._6VtqdG_opBtn:hover{background:var(--hub-active)}._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:var(--hub-hover)}._6VtqdG_row:focus-visible{outline:2px solid var(--hub-accent);outline-offset:-2px}._6VtqdG_rowStatic{cursor:default}._6VtqdG_rowStatic:hover{background:0 0}._6VtqdG_row+._6VtqdG_row{border-top:.5px solid var(--hub-border)}._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{color:var(--hub-text-3);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{border:.5px solid var(--hub-border-2);color:var(--hub-text-2);white-space:nowrap;border-radius:999px;padding:1px 7px;font-size:10.5px}._6VtqdG_badgeReadonly{opacity:.5}._6VtqdG_switch{border:1px solid var(--hub-border-2);background:var(--dsw-alias-bg-module-platform,#8080804d);cursor:pointer;border-radius:999px;flex:none;width:36px;height:21px;padding:2px;transition:background .18s}._6VtqdG_switch:disabled{opacity:.5;cursor:default}._6VtqdG_switchOn{background:var(--dsw-alias-state-business-primary,#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{color:var(--hub-text-3);text-align:center;padding:20px 0;font-size:13px}._6VtqdG_diagRow{border-left:3px solid var(--hub-warn);background:#d299220f;padding:9px 14px;font-size:12px}._6VtqdG_diagPath{color:var(--hub-text-2);word-break:break-all;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}._6VtqdG_diagReason{color:var(--hub-text-3);margin-top:1px}._6VtqdG_updateLink{color:inherit;font-weight:600;text-decoration:underline}._6VtqdG_errorBanner{color:var(--hub-danger);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:var(--hub-success);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{background:var(--hub-hover);color:inherit;cursor:pointer;border:none;border-radius:8px;padding:6px 12px;font-size:12.5px}._6VtqdG_back:hover{background:var(--hub-active)}._6VtqdG_detailName{letter-spacing:-.01em;font-size:19px;font-weight:700}._6VtqdG_detailMeta{color:var(--hub-text-3);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:var(--hub-surface);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:var(--hub-surface);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{color:var(--hub-text-3);font-size:12px}._6VtqdG_input,._6VtqdG_select{background:var(--hub-surface);color:inherit;border:none;border-radius:9px;outline:none;padding:9px 12px;font-size:13px}._6VtqdG_input:focus,._6VtqdG_select:focus{background:var(--hub-active)}._6VtqdG_buttons{align-items:center;gap:8px;display:flex}._6VtqdG_button{cursor:pointer;background:var(--hub-hover);color:inherit;border:none;border-radius:8px;padding:6px 13px;font-size:12.5px;transition:background .15s}._6VtqdG_button:hover{background:var(--hub-active)}._6VtqdG_primary{background:var(--hub-accent);color:var(--hub-on-accent)}._6VtqdG_primary:hover{background:var(--hub-accent);opacity:.92}._6VtqdG_primary:disabled{opacity:.45;cursor:default}._6VtqdG_danger{background:var(--hub-danger);color:var(--hub-on-accent)}._6VtqdG_danger:hover{background:var(--hub-danger);opacity:.92}._6VtqdG_danger:disabled{opacity:.45;cursor:default}._6VtqdG_formError{color:var(--hub-danger)}._6VtqdG_formSuccess{color:var(--hub-success)}._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{color:var(--hub-text-3);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{color:var(--hub-text-3);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{background:var(--hub-hover);opacity:.72;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{color:var(--hub-text-3);white-space:nowrap;margin-left:2px;font-size:12px}._6VtqdG_pluginVersion{color:var(--hub-text-4);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{background:var(--hub-hover);width:22px;height:22px;color:inherit;cursor:pointer;opacity:.55;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:var(--hub-active)}._6VtqdG_filterBar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_filterBar ._6VtqdG_formLabel{margin:0}._6VtqdG_useCount{color:var(--hub-warn);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{color:var(--hub-text-4);white-space:nowrap;margin-left:auto;font-size:11px}._6VtqdG_switchMixed{background:#2f81f773;background:color-mix(in srgb, var(--hub-accent) 45%, transparent)}._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{border:.5px solid var(--hub-border-2);opacity:.8;white-space:nowrap;background:var(--hub-hover);border-radius:999px;padding:1px 7px;font-size:10.5px}._6VtqdG_statusOk{color:var(--hub-success);background:0 0;border-color:currentColor}._6VtqdG_statusUpdated{color:var(--hub-warn);background:#d2992214;border-color:currentColor}._6VtqdG_statusError{color:var(--hub-danger);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:var(--hub-danger);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:var(--hub-surface);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-alias-bg-layer-1,#f5f5f7);min-width:min(320px,100%);max-width:380px;color:var(--dsw-alias-label-primary,#1d1d1f);border-radius:14px;padding:18px 18px 14px;box-shadow:0 12px 40px #00000047,0 2px 8px #00000029}._6VtqdG_dialogTitle{letter-spacing:-.01em;margin:0 0 8px;font-size:15px;font-weight:700}._6VtqdG_dialogText{color:var(--hub-text-3);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{color:var(--hub-text-3);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 var(--dsw-alias-border-l1,#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-accent);outline-offset:-2px;background:#2f81f712}._6VtqdG_scanList{border:.5px solid var(--hub-border-2);background:var(--hub-surface);border-radius:12px;max-height:360px;margin-top:8px;overflow:auto}._6VtqdG_scanProgressTrack{background:var(--hub-surface);border-radius:4px;flex:1;height:6px;overflow:hidden}._6VtqdG_scanProgressFill{background:var(--hub-accent);height:100%;transition:width .2s}._6VtqdG_filterPanel{background:var(--hub-surface);border-radius:10px;flex-wrap:wrap;align-items:center;gap:12px;padding:10px 12px;display:flex}._6VtqdG_filterField{align-items:center;gap:6px;display:inline-flex}._6VtqdG_filterField ._6VtqdG_formLabel{margin:0}._6VtqdG_filterField ._6VtqdG_select{max-width:150px}._6VtqdG_listTools{justify-content:flex-end;align-items:center;gap:8px;padding:0 2px;display:flex}._6VtqdG_opBtn:focus-visible,._6VtqdG_button:focus-visible,._6VtqdG_segBtn:focus-visible,._6VtqdG_disclosure:focus-visible,._6VtqdG_switch:focus-visible,._6VtqdG_legendToggle:focus-visible,._6VtqdG_back:focus-visible,._6VtqdG_statusWrap:focus-visible,._6VtqdG_input:focus-visible,._6VtqdG_select:focus-visible,._6VtqdG_search:focus-visible,._6VtqdG_sourceLink:focus-visible{outline:2px solid var(--hub-accent);outline-offset:2px}._6VtqdG_opBtn{padding:4px 12px}._6VtqdG_segBtn{padding:6px 13px}._6VtqdG_iconBtn,._6VtqdG_legendToggle{width:28px;height:28px}._6VtqdG_dragHandle{opacity:.45;width:16px}._6VtqdG_dragHandle:hover{opacity:1}._6VtqdG_skeleton{background:var(--hub-surface);border-radius:12px;flex-direction:column;gap:8px;padding:14px;display:flex}._6VtqdG_skeletonRow{background:var(--hub-skeleton);border-radius:7px;height:14px;animation:1.4s ease-in-out infinite _6VtqdG_hubPulse}._6VtqdG_skeletonRow:nth-child(2){width:82%}._6VtqdG_skeletonRow:nth-child(3){width:64%}@keyframes _6VtqdG_hubPulse{0%,to{opacity:.55}50%{opacity:1}}@media (prefers-reduced-motion:reduce){._6VtqdG_panel *,._6VtqdG_panel :before,._6VtqdG_panel :after{transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}";
2849
2762
  const tagId = "dsh-skill-hub/panel.module.css";
2850
2763
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
2851
2764
  const tag = document.createElement("style");
@@ -2896,6 +2809,8 @@ window.__ModuleLoader__.load({
2896
2809
  "empty": "_6VtqdG_empty",
2897
2810
  "errorBanner": "_6VtqdG_errorBanner",
2898
2811
  "filterBar": "_6VtqdG_filterBar",
2812
+ "filterField": "_6VtqdG_filterField",
2813
+ "filterPanel": "_6VtqdG_filterPanel",
2899
2814
  "form": "_6VtqdG_form",
2900
2815
  "formError": "_6VtqdG_formError",
2901
2816
  "formLabel": "_6VtqdG_formLabel",
@@ -2913,6 +2828,7 @@ window.__ModuleLoader__.load({
2913
2828
  "hintInline": "_6VtqdG_hintInline",
2914
2829
  "hintLine": "_6VtqdG_hintLine",
2915
2830
  "hintPadded": "_6VtqdG_hintPadded",
2831
+ "hubPulse": "_6VtqdG_hubPulse",
2916
2832
  "iconBtn": "_6VtqdG_iconBtn",
2917
2833
  "input": "_6VtqdG_input",
2918
2834
  "legend": "_6VtqdG_legend",
@@ -2920,7 +2836,7 @@ window.__ModuleLoader__.load({
2920
2836
  "legendItem": "_6VtqdG_legendItem",
2921
2837
  "legendToggle": "_6VtqdG_legendToggle",
2922
2838
  "legendToggleActive": "_6VtqdG_legendToggleActive",
2923
- "muted": "_6VtqdG_muted",
2839
+ "listTools": "_6VtqdG_listTools",
2924
2840
  "opBtn": "_6VtqdG_opBtn",
2925
2841
  "opDanger": "_6VtqdG_opDanger",
2926
2842
  "panel": "_6VtqdG_panel",
@@ -2946,6 +2862,8 @@ window.__ModuleLoader__.load({
2946
2862
  "segBtnActive": "_6VtqdG_segBtnActive",
2947
2863
  "segmented": "_6VtqdG_segmented",
2948
2864
  "select": "_6VtqdG_select",
2865
+ "skeleton": "_6VtqdG_skeleton",
2866
+ "skeletonRow": "_6VtqdG_skeletonRow",
2949
2867
  "sourceCard": "_6VtqdG_sourceCard",
2950
2868
  "sourceCardTitle": "_6VtqdG_sourceCardTitle",
2951
2869
  "sourceLink": "_6VtqdG_sourceLink",
@@ -3034,7 +2952,7 @@ window.__ModuleLoader__.load({
3034
2952
  * panel stays the single owner of state.
3035
2953
  */
3036
2954
  function SkillDetailView(props) {
3037
- const { detail, hubConfig, uses, groupsState, sourcesState, sourceCheck, checkingSource, syncingSource, loading, disabled, onEnable, onBack, onCheck, onSync, onFollowDelete } = props;
2955
+ const { detail, hubConfig, uses, groupsState, sourcesState, sourceCheck, checkingSource, syncingSource, disabled, onEnable, onBack, onCheck, onSync, onFollowDelete } = props;
3038
2956
  const detailSource = sourcesState?.sources.find((source) => source.skills.includes(detail.name));
3039
2957
  const detailCheck = detailSource !== void 0 ? sourceCheck[detailSource.repo] : void 0;
3040
2958
  const [copied, setCopied] = (0, react.useState)(null);
@@ -3260,10 +3178,6 @@ window.__ModuleLoader__.load({
3260
3178
  className: panel_module_css_default.hintLine,
3261
3179
  children: tt("source.private")
3262
3180
  }),
3263
- loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3264
- className: panel_module_css_default.muted,
3265
- children: tt("detail.loading")
3266
- }) : null,
3267
3181
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
3268
3182
  className: panel_module_css_default.detailContent,
3269
3183
  children: detail.content
@@ -3386,19 +3300,19 @@ window.__ModuleLoader__.load({
3386
3300
  //#endregion
3387
3301
  //#region src/client/panel/SkillRow.tsx
3388
3302
  function SkillRow(props) {
3389
- const { skill, hub } = props;
3390
- const stat = hub.uses.get(skill.name);
3303
+ const { skill, uses, hubConfig, busyNames, editMode, tagBusy, duplicateNames, toggle, openDetail, requestDeleteSkill } = props;
3304
+ const stat = uses.get(skill.name);
3391
3305
  const count = stat?.count ?? 0;
3392
3306
  const lastUsed = stat?.lastUsed;
3393
- const isDuplicate = hub.catalog?.duplicateNames?.includes(skill.name) === true;
3307
+ const isDuplicate = duplicateNames.has(skill.name);
3394
3308
  /** 单一状态圆点:模型可调 → 蓝;否则用户可调 → 绿。与聊天 / 菜单同规则。 */
3395
3309
  const dot = skill.invocation.modelInvocable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3396
3310
  className: panel_module_css_default.dot + " " + panel_module_css_default.dotModel,
3397
- style: dotStyle(hub.hubConfig?.dotModelColor),
3311
+ style: dotStyle(hubConfig?.dotModelColor),
3398
3312
  title: tt("legend.model")
3399
3313
  }) : skill.invocation.userInvocable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3400
3314
  className: panel_module_css_default.dot + " " + panel_module_css_default.dotUser,
3401
- style: dotStyle(hub.hubConfig?.dotUserColor),
3315
+ style: dotStyle(hubConfig?.dotUserColor),
3402
3316
  title: tt("legend.user")
3403
3317
  }) : null;
3404
3318
  /** 键盘打开详情:Enter 或空格。仅当焦点在行本身(而非行内按钮)时生效。 */
@@ -3406,7 +3320,7 @@ window.__ModuleLoader__.load({
3406
3320
  if (event.target !== event.currentTarget) return;
3407
3321
  if (event.key === "Enter" || event.key === " ") {
3408
3322
  event.preventDefault();
3409
- hub.openDetail(skill.name);
3323
+ openDetail(skill.name);
3410
3324
  }
3411
3325
  };
3412
3326
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -3415,7 +3329,7 @@ window.__ModuleLoader__.load({
3415
3329
  tabIndex: 0,
3416
3330
  "aria-label": tt("row.open", { name: skill.name }),
3417
3331
  onClick: () => {
3418
- hub.openDetail(skill.name);
3332
+ openDetail(skill.name);
3419
3333
  },
3420
3334
  onKeyDown,
3421
3335
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -3432,7 +3346,7 @@ window.__ModuleLoader__.load({
3432
3346
  title: skill.displayName,
3433
3347
  children: skill.displayName
3434
3348
  }) : null,
3435
- count > 0 && hub.hubConfig?.showUseCount !== false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3349
+ count > 0 && hubConfig?.showUseCount !== false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3436
3350
  className: panel_module_css_default.useCount,
3437
3351
  children: count
3438
3352
  }) : null,
@@ -3442,7 +3356,7 @@ window.__ModuleLoader__.load({
3442
3356
  title: tt("row.duplicateHint"),
3443
3357
  children: tt("row.duplicate")
3444
3358
  }) : null,
3445
- lastUsed !== void 0 && hub.hubConfig?.showUseTime !== false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3359
+ lastUsed !== void 0 && hubConfig?.showUseTime !== false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3446
3360
  className: panel_module_css_default.useTime,
3447
3361
  children: relativeTimeText(lastUsed)
3448
3362
  }) : null
@@ -3458,21 +3372,21 @@ window.__ModuleLoader__.load({
3458
3372
  "aria-checked": true,
3459
3373
  "aria-label": tt("row.disable"),
3460
3374
  className: panel_module_css_default.switch + " " + panel_module_css_default.switchOn,
3461
- disabled: hub.busyNames.has(skill.name),
3375
+ disabled: busyNames.has(skill.name),
3462
3376
  onClick: (event) => {
3463
3377
  event.stopPropagation();
3464
- hub.toggle(skill, false);
3378
+ toggle(skill, false);
3465
3379
  },
3466
3380
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.switchThumb })
3467
- }), hub.editMode ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3381
+ }), editMode ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3468
3382
  type: "button",
3469
3383
  className: panel_module_css_default.opBtn + " " + panel_module_css_default.opDanger + " " + panel_module_css_default.iconBtn,
3470
- disabled: hub.busyNames.has(skill.name) || hub.tagBusy,
3384
+ disabled: busyNames.has(skill.name) || tagBusy,
3471
3385
  title: tt("row.delete"),
3472
3386
  "aria-label": tt("row.delete"),
3473
3387
  onClick: (event) => {
3474
3388
  event.stopPropagation();
3475
- hub.requestDeleteSkill(skill.name);
3389
+ requestDeleteSkill(skill.name);
3476
3390
  },
3477
3391
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconTrashOutline16, { size: 14 })
3478
3392
  }) : null] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -3547,27 +3461,363 @@ window.__ModuleLoader__.load({
3547
3461
  //#endregion
3548
3462
  //#region src/client/panel/GroupSummary.tsx
3549
3463
  function GroupSummary(props) {
3550
- const { members, hub } = props;
3464
+ const { members, uses, hubConfig } = props;
3551
3465
  let total = 0;
3552
3466
  let latest;
3553
3467
  for (const name of members) {
3554
- const stat = hub.uses.get(name);
3468
+ const stat = uses.get(name);
3555
3469
  if (stat === void 0) continue;
3556
3470
  total += stat.count;
3557
3471
  if (stat.lastUsed !== void 0 && (latest === void 0 || stat.lastUsed > latest)) latest = stat.lastUsed;
3558
3472
  }
3559
3473
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3560
3474
  className: panel_module_css_default.groupTitleInner,
3561
- children: [hub.hubConfig?.showGroupSummary !== false && total > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3475
+ children: [hubConfig?.showGroupSummary !== false && total > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3562
3476
  className: panel_module_css_default.useCount,
3563
3477
  children: total
3564
- }) : null, hub.hubConfig?.showGroupSummary !== false && latest !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3478
+ }) : null, hubConfig?.showGroupSummary !== false && latest !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3565
3479
  className: panel_module_css_default.useTime + " " + panel_module_css_default.groupTime,
3566
3480
  children: relativeTimeText(latest)
3567
3481
  }) : null]
3568
3482
  });
3569
3483
  }
3570
3484
  //#endregion
3485
+ //#region src/client/panel/useDragReorder.ts
3486
+ /**
3487
+ * Build the drag-reorder props for one container key.
3488
+ * @param options - the list's drag state and its reorder callback.
3489
+ * @returns a factory producing the props for a given container key.
3490
+ */
3491
+ function useDragReorder(options) {
3492
+ const { dragKey, overKey, setDragKey, setOverKey, onDrop } = options;
3493
+ return (key) => {
3494
+ const isDragging = dragKey === key;
3495
+ const isOver = overKey === key && dragKey !== key;
3496
+ return {
3497
+ className: panel_module_css_default.section + (isDragging ? " " + panel_module_css_default.dragging : "") + (isOver ? " " + panel_module_css_default.dragOver : ""),
3498
+ draggable: true,
3499
+ onDragStart: (event) => {
3500
+ setDragKey(key);
3501
+ event.dataTransfer.effectAllowed = "move";
3502
+ event.dataTransfer.setData("text/plain", key);
3503
+ },
3504
+ onDragOver: (event) => {
3505
+ event.preventDefault();
3506
+ if (overKey !== key) setOverKey(key);
3507
+ },
3508
+ onDragLeave: () => {
3509
+ if (overKey === key) setOverKey(null);
3510
+ },
3511
+ onDrop: (event) => {
3512
+ event.preventDefault();
3513
+ onDrop(key);
3514
+ setOverKey(null);
3515
+ },
3516
+ onDragEnd: () => {
3517
+ setDragKey(null);
3518
+ setOverKey(null);
3519
+ }
3520
+ };
3521
+ };
3522
+ }
3523
+ //#endregion
3524
+ //#region src/client/panel/ReorderButtons.tsx
3525
+ function ReorderButtons(props) {
3526
+ const { canMoveUp, canMoveDown, onMove } = props;
3527
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3528
+ type: "button",
3529
+ className: panel_module_css_default.opBtn + " " + panel_module_css_default.iconBtn,
3530
+ disabled: !canMoveUp,
3531
+ "aria-label": tt("reorder.up"),
3532
+ title: tt("reorder.up"),
3533
+ onClick: (event) => {
3534
+ event.stopPropagation();
3535
+ onMove(-1);
3536
+ },
3537
+ children: "↑"
3538
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3539
+ type: "button",
3540
+ className: panel_module_css_default.opBtn + " " + panel_module_css_default.iconBtn,
3541
+ disabled: !canMoveDown,
3542
+ "aria-label": tt("reorder.down"),
3543
+ title: tt("reorder.down"),
3544
+ onClick: (event) => {
3545
+ event.stopPropagation();
3546
+ onMove(1);
3547
+ },
3548
+ children: "↓"
3549
+ })] });
3550
+ }
3551
+ //#endregion
3552
+ //#region src/client/panel/ProjectTree.tsx
3553
+ function ProjectTree(props) {
3554
+ const { skills, collapsedGroups, toggleGroupCollapse, subdividedProjects, toggleSubdivide, rowProps, dragProps, editMode, canMoveUp, canMoveDown, onMove } = props;
3555
+ const topCollapsed = collapsedGroups.has("project");
3556
+ const byProject = /* @__PURE__ */ new Map();
3557
+ for (const skill of skills) {
3558
+ const key = skill.workspace ?? skill.source;
3559
+ const entry = byProject.get(key);
3560
+ if (entry === void 0) byProject.set(key, {
3561
+ title: skill.workspaceTitle ?? skill.workspace ?? tt("groups.project"),
3562
+ skills: [skill]
3563
+ });
3564
+ else entry.skills.push(skill);
3565
+ }
3566
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3567
+ ...dragProps,
3568
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3569
+ className: panel_module_css_default.groupHead,
3570
+ children: [
3571
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3572
+ className: panel_module_css_default.dragHandle,
3573
+ "aria-hidden": true,
3574
+ title: tt("drag.reorder"),
3575
+ children: "⋮⋮"
3576
+ }),
3577
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3578
+ type: "button",
3579
+ className: panel_module_css_default.disclosure,
3580
+ "aria-expanded": !topCollapsed,
3581
+ onClick: () => {
3582
+ toggleGroupCollapse("project");
3583
+ },
3584
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.chevron + (topCollapsed ? " " + panel_module_css_default.chevronCollapsed : "") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3585
+ className: panel_module_css_default.groupTitle,
3586
+ children: [
3587
+ tt("groups.project"),
3588
+ " · ",
3589
+ byProject.size
3590
+ ]
3591
+ })]
3592
+ }),
3593
+ editMode ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3594
+ className: panel_module_css_default.groupOps,
3595
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReorderButtons, {
3596
+ canMoveUp,
3597
+ canMoveDown,
3598
+ onMove
3599
+ })
3600
+ }) : null
3601
+ ]
3602
+ }), !topCollapsed ? [...byProject.entries()].map(([key, proj]) => {
3603
+ const projKey = "project:" + key;
3604
+ const projCollapsed = collapsedGroups.has(projKey);
3605
+ const subdivided = subdividedProjects.has(key);
3606
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3607
+ className: panel_module_css_default.projectNest,
3608
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3609
+ className: panel_module_css_default.groupHead,
3610
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3611
+ type: "button",
3612
+ className: panel_module_css_default.disclosure,
3613
+ "aria-expanded": !projCollapsed,
3614
+ onClick: () => {
3615
+ toggleGroupCollapse(projKey);
3616
+ },
3617
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.chevron + (projCollapsed ? " " + panel_module_css_default.chevronCollapsed : "") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3618
+ className: panel_module_css_default.groupTitle,
3619
+ children: [
3620
+ proj.title,
3621
+ " · ",
3622
+ proj.skills.length,
3623
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupSummary, {
3624
+ members: proj.skills.map((s) => s.name),
3625
+ uses: rowProps.uses,
3626
+ hubConfig: rowProps.hubConfig
3627
+ })
3628
+ ]
3629
+ })]
3630
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3631
+ className: panel_module_css_default.groupOps,
3632
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3633
+ type: "button",
3634
+ className: panel_module_css_default.opBtn,
3635
+ onClick: (event) => {
3636
+ event.stopPropagation();
3637
+ toggleSubdivide(key);
3638
+ },
3639
+ children: subdivided ? tt("groups.merge") : tt("groups.subdivide")
3640
+ })
3641
+ })]
3642
+ }), !projCollapsed ? subdivided ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3643
+ className: panel_module_css_default.projectNest,
3644
+ children: ["project-dsh", "project-agents"].map((source) => {
3645
+ const list = proj.skills.filter((skill) => skill.source === source);
3646
+ if (list.length === 0) return null;
3647
+ const srcKey = projKey + ":" + source;
3648
+ const srcCollapsed = collapsedGroups.has(srcKey);
3649
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3650
+ className: panel_module_css_default.projectNest,
3651
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3652
+ className: panel_module_css_default.groupHead,
3653
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3654
+ type: "button",
3655
+ className: panel_module_css_default.disclosure,
3656
+ "aria-expanded": !srcCollapsed,
3657
+ onClick: () => {
3658
+ toggleGroupCollapse(srcKey);
3659
+ },
3660
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.chevron + (srcCollapsed ? " " + panel_module_css_default.chevronCollapsed : "") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3661
+ className: panel_module_css_default.groupTitle,
3662
+ children: [
3663
+ tt("badge.source." + source),
3664
+ " · ",
3665
+ list.length
3666
+ ]
3667
+ })]
3668
+ })
3669
+ }), !srcCollapsed ? list.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillRow, {
3670
+ skill,
3671
+ ...rowProps
3672
+ }, skill.name)) : null]
3673
+ }, srcKey);
3674
+ })
3675
+ }) : proj.skills.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillRow, {
3676
+ skill,
3677
+ ...rowProps
3678
+ }, skill.name)) : null]
3679
+ }, projKey);
3680
+ }) : null]
3681
+ });
3682
+ }
3683
+ //#endregion
3684
+ //#region src/client/panel/GroupSwitchButton.tsx
3685
+ function GroupSwitchButton(props) {
3686
+ const { state, label, memberCount, batchBusy, hasWritable, onToggle } = props;
3687
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3688
+ type: "button",
3689
+ role: "switch",
3690
+ "aria-checked": state !== "off",
3691
+ "aria-label": label,
3692
+ className: panel_module_css_default.switch + (state === "on" ? " " + panel_module_css_default.switchOn : state === "mixed" ? " " + panel_module_css_default.switchMixed : ""),
3693
+ disabled: batchBusy || memberCount === 0 || state !== "off" && !hasWritable,
3694
+ title: state !== "off" && !hasWritable ? tt("groups.noWritable") : void 0,
3695
+ onClick: (event) => {
3696
+ event.stopPropagation();
3697
+ onToggle();
3698
+ },
3699
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.switchThumb })
3700
+ });
3701
+ }
3702
+ //#endregion
3703
+ //#region src/client/panel/CollectionCard.tsx
3704
+ function CollectionCard(props) {
3705
+ const { collection, skills, disabledMembers, collapsed, view, check, hasWritable, editMode, canMoveUp, canMoveDown, onMove, checkingSource, syncingSource, batchBusy, rowProps, dragProps, toggleGroupCollapse, checkSources, requestSync, requestDelete, toggleGroup, requestDeleteGroup, enableDisabled, openDetail } = props;
3706
+ const { busyNames, duplicateNames, uses, hubConfig } = rowProps;
3707
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3708
+ ...dragProps,
3709
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3710
+ className: panel_module_css_default.groupHead,
3711
+ children: [
3712
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3713
+ className: panel_module_css_default.dragHandle,
3714
+ "aria-hidden": true,
3715
+ title: tt("drag.reorder"),
3716
+ children: "⋮⋮"
3717
+ }),
3718
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3719
+ type: "button",
3720
+ className: panel_module_css_default.disclosure,
3721
+ "aria-expanded": !collapsed,
3722
+ onClick: () => {
3723
+ toggleGroupCollapse("col:" + collection.name);
3724
+ },
3725
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.chevron + (collapsed ? " " + panel_module_css_default.chevronCollapsed : "") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3726
+ className: panel_module_css_default.groupTitle,
3727
+ children: [
3728
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
3729
+ className: panel_module_css_default.sourceLink,
3730
+ href: "https://github.com/" + collection.name,
3731
+ target: "_blank",
3732
+ rel: "noreferrer",
3733
+ onClick: (event) => {
3734
+ event.stopPropagation();
3735
+ },
3736
+ children: collection.name
3737
+ }),
3738
+ " · " + collection.skillNames.length,
3739
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupSummary, {
3740
+ members: collection.skillNames,
3741
+ uses,
3742
+ hubConfig
3743
+ })
3744
+ ]
3745
+ })]
3746
+ }),
3747
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3748
+ className: panel_module_css_default.groupOps,
3749
+ children: [
3750
+ editMode ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReorderButtons, {
3751
+ canMoveUp,
3752
+ canMoveDown,
3753
+ onMove
3754
+ }) : null,
3755
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SourceStatusBadge, {
3756
+ check,
3757
+ checking: checkingSource === collection.name,
3758
+ onCheck: () => {
3759
+ checkSources(collection.name);
3760
+ }
3761
+ }),
3762
+ check !== void 0 && check.changed && check.updated.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3763
+ type: "button",
3764
+ className: panel_module_css_default.opBtn,
3765
+ disabled: syncingSource !== null,
3766
+ onClick: (event) => {
3767
+ event.stopPropagation();
3768
+ requestSync(collection.name, check.updated);
3769
+ },
3770
+ children: syncingSource === collection.name ? tt("source.syncing") : tt("source.sync")
3771
+ }) : null,
3772
+ check !== void 0 && check.deleted.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3773
+ type: "button",
3774
+ className: panel_module_css_default.opBtn + " " + panel_module_css_default.opDanger,
3775
+ onClick: (event) => {
3776
+ event.stopPropagation();
3777
+ requestDelete(collection.name, check.deleted);
3778
+ },
3779
+ children: tt("source.followDelete")
3780
+ }) : null,
3781
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupSwitchButton, {
3782
+ state: view.state,
3783
+ label: collection.name,
3784
+ memberCount: collection.skillNames.length,
3785
+ batchBusy,
3786
+ hasWritable,
3787
+ onToggle: () => {
3788
+ toggleGroup("col:" + collection.name, collection.name, view.state);
3789
+ }
3790
+ }),
3791
+ editMode ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3792
+ type: "button",
3793
+ className: panel_module_css_default.opBtn + " " + panel_module_css_default.opDanger,
3794
+ title: tt("source.deleteGroupHint", { count: collection.skillNames.length }),
3795
+ onClick: (event) => {
3796
+ event.stopPropagation();
3797
+ requestDeleteGroup(collection.name, collection.skillNames);
3798
+ },
3799
+ children: tt("source.deleteGroup")
3800
+ }) : null
3801
+ ]
3802
+ })
3803
+ ]
3804
+ }), !collapsed ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [skills.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillRow, {
3805
+ skill,
3806
+ ...rowProps
3807
+ }, skill.name)), disabledMembers.map((record) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DisabledRow, {
3808
+ record,
3809
+ busy: busyNames.has(record.name),
3810
+ duplicate: duplicateNames.has(record.name),
3811
+ onEnable: () => {
3812
+ enableDisabled(record);
3813
+ },
3814
+ onOpen: () => {
3815
+ openDetail(record.name);
3816
+ }
3817
+ }, record.name))] }) : null]
3818
+ });
3819
+ }
3820
+ //#endregion
3571
3821
  //#region src/client/panel/SourcesView.tsx
3572
3822
  /**
3573
3823
  * Sources tab: the flat skill list or the grouped view — a project-level
@@ -3578,19 +3828,18 @@ window.__ModuleLoader__.load({
3578
3828
  */
3579
3829
  function SourcesView(props) {
3580
3830
  const { hub } = props;
3581
- const { catalog, groupsState, skillView, sourceFilter, origins, sorted, normalized, collapsedGroups, viewNames, sourceCheck, actionNames, checkingSource, syncingSource, batchBusy, busyNames, toggleGroupCollapse, checkSources, requestSync, requestDelete, requestDeleteGroup, toggleGroup, enableDisabled } = hub;
3831
+ const { catalog, groupsState, skillView, sourceFilter, origins, sorted, normalized, collapsedGroups, viewNames, sourceCheck, actionNames, checkingSource, syncingSource, batchBusy, busyNames, toggleGroupCollapse, setAllGroupsCollapsed, checkSources, requestSync, requestDelete, requestDeleteGroup, toggleGroup, enableDisabled } = hub;
3582
3832
  const [topDragKey, setTopDragKey] = (0, react.useState)(null);
3583
3833
  const [topOverKey, setTopOverKey] = (0, react.useState)(null);
3584
- if (skillView === "flat") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: filterBySource(sorted, sourceFilter, origins).map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillRow, {
3585
- skill,
3586
- hub
3587
- }, skill.name)) });
3834
+ /** 重复技能名集合:整表只建一次,行内用 has 取代逐行线性 includes。 */
3835
+ const duplicateNames = (0, react.useMemo)(() => new Set(catalog?.duplicateNames ?? []), [catalog]);
3588
3836
  const projectSkillsAll = filterBySource(sorted, sourceFilter, origins).filter((skill) => isProjectSource(skill.source));
3589
3837
  const hasProject = projectSkillsAll.length > 0;
3590
3838
  const collections = groupsState?.collections ?? [];
3591
3839
  const uncategorized = filterBySource(sorted, sourceFilter, origins).filter((skill) => origins[skill.name] === void 0 && !isProjectSource(skill.source));
3592
- const personalDisabledAll = (catalog?.disabled ?? []).filter((record) => origins[record.name] === void 0).filter((record) => normalized.length === 0 || record.name.toLocaleLowerCase().includes(normalized) || record.description.toLocaleLowerCase().includes(normalized)).filter((record) => sourceFilter === "all" || sourceFilter === "private");
3593
- const hasPersonal = [...uncategorized.map((s) => s.name), ...personalDisabledAll.map((r) => r.name)].length > 0;
3840
+ const personalDisabled = (catalog?.disabled ?? []).filter((record) => origins[record.name] === void 0).filter((record) => normalized.length === 0 || record.name.toLocaleLowerCase().includes(normalized) || record.description.toLocaleLowerCase().includes(normalized)).filter((record) => sourceFilter === "all" || sourceFilter === "private");
3841
+ const allPersonalNames = [...uncategorized.map((s) => s.name), ...personalDisabled.map((r) => r.name)];
3842
+ const hasPersonal = allPersonalNames.length > 0;
3594
3843
  const defaultTopKeys = [
3595
3844
  ...hasProject ? ["project"] : [],
3596
3845
  ...collections.map((c) => "col:" + c.name),
@@ -3615,389 +3864,185 @@ window.__ModuleLoader__.load({
3615
3864
  next.splice(to, 0, moved);
3616
3865
  hub.reorderSourceGroups(next);
3617
3866
  };
3618
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [!hasProject && collections.length === 0 && !hasPersonal ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3619
- className: panel_module_css_default.empty,
3620
- children: tt("groups.noCollections")
3621
- }) : null, topOrderedKeys.map((topKey) => {
3622
- if (topKey === "project" && hasProject) {
3623
- const topCollapsed = collapsedGroups.has("project");
3624
- const isDragging = topDragKey === "project";
3625
- const isOver = topOverKey === "project" && topDragKey !== "project";
3626
- const byProject = /* @__PURE__ */ new Map();
3627
- for (const skill of projectSkillsAll) {
3628
- const key = skill.workspace ?? skill.source;
3629
- const entry = byProject.get(key);
3630
- if (entry === void 0) byProject.set(key, {
3631
- title: skill.workspaceTitle ?? skill.workspace ?? tt("groups.project"),
3632
- skills: [skill]
3633
- });
3634
- else entry.skills.push(skill);
3635
- }
3636
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3637
- className: panel_module_css_default.section + (isDragging ? " " + panel_module_css_default.dragging : "") + (isOver ? " " + panel_module_css_default.dragOver : ""),
3638
- draggable: true,
3639
- onDragStart: (e) => {
3640
- setTopDragKey("project");
3641
- e.dataTransfer.effectAllowed = "move";
3642
- e.dataTransfer.setData("text/plain", "project");
3643
- },
3644
- onDragOver: (e) => {
3645
- e.preventDefault();
3646
- if (topOverKey !== "project") setTopOverKey("project");
3647
- },
3648
- onDragLeave: () => {
3649
- if (topOverKey === "project") setTopOverKey(null);
3650
- },
3651
- onDrop: (e) => {
3652
- e.preventDefault();
3653
- handleTopDrop("project");
3654
- setTopOverKey(null);
3655
- },
3656
- onDragEnd: () => {
3657
- setTopDragKey(null);
3658
- setTopOverKey(null);
3659
- },
3660
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3661
- className: panel_module_css_default.groupHead,
3662
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3663
- className: panel_module_css_default.dragHandle,
3664
- "aria-hidden": true,
3665
- title: "拖拽调整顺序",
3666
- children: "⋮⋮"
3667
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3668
- type: "button",
3669
- className: panel_module_css_default.disclosure,
3670
- "aria-expanded": !topCollapsed,
3671
- onClick: () => {
3672
- toggleGroupCollapse("project");
3673
- },
3674
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.chevron + (topCollapsed ? " " + panel_module_css_default.chevronCollapsed : "") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3675
- className: panel_module_css_default.groupTitle,
3676
- children: [
3677
- tt("groups.project"),
3678
- " · ",
3679
- byProject.size
3680
- ]
3681
- })]
3682
- })]
3683
- }), !topCollapsed ? [...byProject.entries()].map(([key, proj]) => {
3684
- const projKey = "project:" + key;
3685
- const projCollapsed = collapsedGroups.has(projKey);
3686
- const subdivided = hub.subdividedProjects.has(key);
3687
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3688
- className: panel_module_css_default.projectNest,
3689
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3690
- className: panel_module_css_default.groupHead,
3691
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3867
+ const drag = useDragReorder({
3868
+ dragKey: topDragKey,
3869
+ overKey: topOverKey,
3870
+ setDragKey: setTopDragKey,
3871
+ setOverKey: setTopOverKey,
3872
+ onDrop: handleTopDrop
3873
+ });
3874
+ /** 顶层分组是否已全部折叠(决定「全部折叠/展开」按钮的文案)。 */
3875
+ const allTopCollapsed = topOrderedKeys.length > 0 && topOrderedKeys.every((key) => collapsedGroups.has(key));
3876
+ /** 键盘可用的排序:与相邻项交换后落盘(与拖拽走同一条 store 路径)。 */
3877
+ const moveTop = (key, direction) => {
3878
+ const from = topOrderedKeys.indexOf(key);
3879
+ const to = from + direction;
3880
+ if (from === -1 || to < 0 || to >= topOrderedKeys.length) return;
3881
+ const next = [...topOrderedKeys];
3882
+ const [moved] = next.splice(from, 1);
3883
+ next.splice(to, 0, moved);
3884
+ hub.reorderSourceGroups(next);
3885
+ };
3886
+ /** SkillRow 收窄后的 props:父组件统一传入它实际消费的字段。 */
3887
+ const rowProps = {
3888
+ uses: hub.uses,
3889
+ hubConfig: hub.hubConfig,
3890
+ busyNames,
3891
+ editMode: hub.editMode,
3892
+ tagBusy: hub.tagBusy,
3893
+ duplicateNames,
3894
+ toggle: hub.toggle,
3895
+ openDetail: hub.openDetail,
3896
+ requestDeleteSkill: hub.requestDeleteSkill
3897
+ };
3898
+ if (skillView === "flat") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: filterBySource(sorted, sourceFilter, origins).map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillRow, {
3899
+ skill,
3900
+ ...rowProps
3901
+ }, skill.name)) });
3902
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
3903
+ !hasProject && collections.length === 0 && !hasPersonal ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3904
+ className: panel_module_css_default.empty,
3905
+ children: tt("groups.noCollections")
3906
+ }) : null,
3907
+ topOrderedKeys.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3908
+ className: panel_module_css_default.listTools,
3909
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3910
+ type: "button",
3911
+ className: panel_module_css_default.opBtn,
3912
+ onClick: () => {
3913
+ setAllGroupsCollapsed(allTopCollapsed ? null : topOrderedKeys);
3914
+ },
3915
+ children: allTopCollapsed ? tt("groups.expandAll") : tt("groups.collapseAll")
3916
+ })
3917
+ }) : null,
3918
+ topOrderedKeys.map((topKey) => {
3919
+ if (topKey === "project" && hasProject) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProjectTree, {
3920
+ editMode: hub.editMode,
3921
+ canMoveUp: topOrderedKeys.indexOf(topKey) > 0,
3922
+ canMoveDown: topOrderedKeys.indexOf(topKey) < topOrderedKeys.length - 1,
3923
+ onMove: (direction) => {
3924
+ moveTop(topKey, direction);
3925
+ },
3926
+ skills: projectSkillsAll,
3927
+ collapsedGroups,
3928
+ toggleGroupCollapse,
3929
+ subdividedProjects: hub.subdividedProjects,
3930
+ toggleSubdivide: hub.toggleSubdivide,
3931
+ rowProps,
3932
+ dragProps: drag("project")
3933
+ }, "project");
3934
+ if (topKey.startsWith("col:")) {
3935
+ const colName = topKey.slice(4);
3936
+ const collection = collections.find((c) => c.name === colName);
3937
+ if (collection === void 0) return null;
3938
+ const skills = filterBySource(sorted, sourceFilter, origins).filter((skill) => collection.skillNames.includes(skill.name));
3939
+ const disabledMembers = (catalog?.disabled ?? []).filter((record) => collection.skillNames.includes(record.name) && (normalized.length === 0 || record.name.toLocaleLowerCase().includes(normalized) || record.description.toLocaleLowerCase().includes(normalized)) && (sourceFilter === "all" || (origins[record.name] ?? "private") === sourceFilter));
3940
+ const collapsed = collapsedGroups.has("col:" + collection.name);
3941
+ const view = groupSwitchView(collection.skillNames, viewNames);
3942
+ const check = sourceCheck[collection.name];
3943
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CollectionCard, {
3944
+ collection,
3945
+ skills,
3946
+ disabledMembers,
3947
+ collapsed,
3948
+ view,
3949
+ check,
3950
+ hasWritable: collection.skillNames.some((name) => actionNames.has(name)),
3951
+ editMode: hub.editMode,
3952
+ canMoveUp: topOrderedKeys.indexOf(topKey) > 0,
3953
+ canMoveDown: topOrderedKeys.indexOf(topKey) < topOrderedKeys.length - 1,
3954
+ onMove: (direction) => {
3955
+ moveTop(topKey, direction);
3956
+ },
3957
+ checkingSource,
3958
+ syncingSource,
3959
+ batchBusy,
3960
+ rowProps,
3961
+ dragProps: drag(topKey),
3962
+ toggleGroupCollapse,
3963
+ checkSources,
3964
+ requestSync,
3965
+ requestDelete,
3966
+ toggleGroup,
3967
+ requestDeleteGroup,
3968
+ enableDisabled,
3969
+ openDetail: hub.openDetail
3970
+ }, "col:" + collection.name);
3971
+ }
3972
+ if (topKey === "uncategorized-source" && hasPersonal) {
3973
+ if (allPersonalNames.length === 0) return null;
3974
+ const collapsed = collapsedGroups.has("uncategorized-source");
3975
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3976
+ ...drag("uncategorized-source"),
3977
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3978
+ className: panel_module_css_default.groupHead,
3979
+ children: [
3980
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3981
+ className: panel_module_css_default.dragHandle,
3982
+ "aria-hidden": true,
3983
+ title: tt("drag.reorder"),
3984
+ children: "⋮⋮"
3985
+ }),
3986
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3692
3987
  type: "button",
3693
3988
  className: panel_module_css_default.disclosure,
3694
- "aria-expanded": !projCollapsed,
3989
+ "aria-expanded": !collapsed,
3695
3990
  onClick: () => {
3696
- toggleGroupCollapse(projKey);
3991
+ toggleGroupCollapse("uncategorized-source");
3697
3992
  },
3698
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.chevron + (projCollapsed ? " " + panel_module_css_default.chevronCollapsed : "") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3993
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.chevron + (collapsed ? " " + panel_module_css_default.chevronCollapsed : "") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3699
3994
  className: panel_module_css_default.groupTitle,
3700
3995
  children: [
3701
- proj.title,
3996
+ tt("groups.personal"),
3702
3997
  " · ",
3703
- proj.skills.length,
3998
+ allPersonalNames.length,
3704
3999
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupSummary, {
3705
- members: proj.skills.map((s) => s.name),
3706
- hub
4000
+ members: allPersonalNames,
4001
+ uses: hub.uses,
4002
+ hubConfig: hub.hubConfig
3707
4003
  })
3708
4004
  ]
3709
4005
  })]
3710
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4006
+ }),
4007
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3711
4008
  className: panel_module_css_default.groupOps,
3712
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3713
- type: "button",
3714
- className: panel_module_css_default.opBtn,
3715
- onClick: (event) => {
3716
- event.stopPropagation();
3717
- hub.toggleSubdivide(key);
3718
- },
3719
- children: subdivided ? tt("groups.merge") : tt("groups.subdivide")
3720
- })
3721
- })]
3722
- }), !projCollapsed ? subdivided ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3723
- className: panel_module_css_default.projectNest,
3724
- children: ["project-dsh", "project-agents"].map((source) => {
3725
- const list = proj.skills.filter((skill) => skill.source === source);
3726
- if (list.length === 0) return null;
3727
- const srcKey = projKey + ":" + source;
3728
- const srcCollapsed = collapsedGroups.has(srcKey);
3729
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3730
- className: panel_module_css_default.projectNest,
3731
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3732
- className: panel_module_css_default.groupHead,
3733
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3734
- type: "button",
3735
- className: panel_module_css_default.disclosure,
3736
- "aria-expanded": !srcCollapsed,
3737
- onClick: () => {
3738
- toggleGroupCollapse(srcKey);
3739
- },
3740
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.chevron + (srcCollapsed ? " " + panel_module_css_default.chevronCollapsed : "") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3741
- className: panel_module_css_default.groupTitle,
3742
- children: [
3743
- tt("badge.source." + source),
3744
- " · ",
3745
- list.length
3746
- ]
3747
- })]
3748
- })
3749
- }), !srcCollapsed ? list.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillRow, {
3750
- skill,
3751
- hub
3752
- }, skill.name)) : null]
3753
- }, srcKey);
3754
- })
3755
- }) : proj.skills.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillRow, {
3756
- skill,
3757
- hub
3758
- }, skill.name)) : null]
3759
- }, projKey);
3760
- }) : null]
3761
- }, "project");
3762
- }
3763
- if (topKey.startsWith("col:")) {
3764
- const colName = topKey.slice(4);
3765
- const collection = collections.find((c) => c.name === colName);
3766
- if (collection === void 0) return null;
3767
- const skills = filterBySource(sorted, sourceFilter, origins).filter((skill) => collection.skillNames.includes(skill.name));
3768
- const disabledMembers = (catalog?.disabled ?? []).filter((record) => collection.skillNames.includes(record.name) && (normalized.length === 0 || record.name.toLocaleLowerCase().includes(normalized) || record.description.toLocaleLowerCase().includes(normalized)) && (sourceFilter === "all" || (origins[record.name] ?? "private") === sourceFilter));
3769
- const collapsed = collapsedGroups.has("col:" + collection.name);
3770
- const view = groupSwitchView(collection.skillNames, viewNames);
3771
- const check = sourceCheck[collection.name];
3772
- const hasWritable = collection.skillNames.some((name) => actionNames.has(name));
3773
- const isDragging = topDragKey === topKey;
3774
- const isOver = topOverKey === topKey && topDragKey !== topKey;
3775
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3776
- className: panel_module_css_default.section + (isDragging ? " " + panel_module_css_default.dragging : "") + (isOver ? " " + panel_module_css_default.dragOver : ""),
3777
- draggable: true,
3778
- onDragStart: (e) => {
3779
- setTopDragKey(topKey);
3780
- e.dataTransfer.effectAllowed = "move";
3781
- e.dataTransfer.setData("text/plain", topKey);
3782
- },
3783
- onDragOver: (e) => {
3784
- e.preventDefault();
3785
- if (topOverKey !== topKey) setTopOverKey(topKey);
3786
- },
3787
- onDragLeave: () => {
3788
- if (topOverKey === topKey) setTopOverKey(null);
3789
- },
3790
- onDrop: (e) => {
3791
- e.preventDefault();
3792
- handleTopDrop(topKey);
3793
- setTopOverKey(null);
3794
- },
3795
- onDragEnd: () => {
3796
- setTopDragKey(null);
3797
- setTopOverKey(null);
3798
- },
3799
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3800
- className: panel_module_css_default.groupHead,
3801
- children: [
3802
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3803
- className: panel_module_css_default.dragHandle,
3804
- "aria-hidden": true,
3805
- title: "拖拽调整顺序",
3806
- children: "⋮⋮"
3807
- }),
3808
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3809
- type: "button",
3810
- className: panel_module_css_default.disclosure,
3811
- "aria-expanded": !collapsed,
3812
- onClick: () => {
3813
- toggleGroupCollapse("col:" + collection.name);
3814
- },
3815
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.chevron + (collapsed ? " " + panel_module_css_default.chevronCollapsed : "") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3816
- className: panel_module_css_default.groupTitle,
3817
- children: [
3818
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
3819
- className: panel_module_css_default.sourceLink,
3820
- href: "https://github.com/" + collection.name,
3821
- target: "_blank",
3822
- rel: "noreferrer",
3823
- onClick: (event) => {
3824
- event.stopPropagation();
3825
- },
3826
- children: collection.name
3827
- }),
3828
- " · " + collection.skillNames.length,
3829
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupSummary, {
3830
- members: collection.skillNames,
3831
- hub
3832
- })
3833
- ]
3834
- })]
3835
- }),
3836
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3837
- className: panel_module_css_default.groupOps,
3838
- children: [
3839
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SourceStatusBadge, {
3840
- check,
3841
- checking: checkingSource === collection.name,
3842
- onCheck: () => {
3843
- checkSources(collection.name);
4009
+ children: hub.editMode ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReorderButtons, {
4010
+ canMoveUp: topOrderedKeys.indexOf(topKey) > 0,
4011
+ canMoveDown: topOrderedKeys.indexOf(topKey) < topOrderedKeys.length - 1,
4012
+ onMove: (direction) => {
4013
+ moveTop(topKey, direction);
3844
4014
  }
3845
- }),
3846
- check !== void 0 && check.changed && check.updated.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3847
- type: "button",
3848
- className: panel_module_css_default.opBtn,
3849
- disabled: syncingSource !== null,
3850
- onClick: (event) => {
3851
- event.stopPropagation();
3852
- requestSync(collection.name, check.updated);
3853
- },
3854
- children: syncingSource === collection.name ? tt("source.syncing") : tt("source.sync")
3855
- }) : null,
3856
- check !== void 0 && check.deleted.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3857
- type: "button",
3858
- className: panel_module_css_default.opBtn + " " + panel_module_css_default.opDanger,
3859
- onClick: (event) => {
3860
- event.stopPropagation();
3861
- requestDelete(collection.name, check.deleted);
3862
- },
3863
- children: tt("source.followDelete")
3864
- }) : null,
3865
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3866
- type: "button",
3867
- role: "switch",
3868
- "aria-checked": view.state !== "off",
3869
- "aria-label": collection.name,
3870
- className: panel_module_css_default.switch + (view.state === "on" ? " " + panel_module_css_default.switchOn : view.state === "mixed" ? " " + panel_module_css_default.switchMixed : ""),
3871
- disabled: batchBusy || collection.skillNames.length === 0 || view.state !== "off" && !hasWritable,
3872
- title: view.state !== "off" && !hasWritable ? tt("groups.noWritable") : void 0,
3873
- onClick: (event) => {
3874
- event.stopPropagation();
3875
- toggleGroup("col:" + collection.name, collection.name, view.state);
3876
- },
3877
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.switchThumb })
3878
- }),
3879
- hub.editMode ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4015
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3880
4016
  type: "button",
3881
4017
  className: panel_module_css_default.opBtn + " " + panel_module_css_default.opDanger,
3882
- title: tt("source.deleteGroupHint", { count: collection.skillNames.length }),
4018
+ title: tt("source.deleteGroupHint", { count: allPersonalNames.length }),
3883
4019
  onClick: (event) => {
3884
4020
  event.stopPropagation();
3885
- requestDeleteGroup(collection.name, collection.skillNames);
4021
+ requestDeleteGroup(tt("groups.personal"), allPersonalNames);
3886
4022
  },
3887
4023
  children: tt("source.deleteGroup")
3888
- }) : null
3889
- ]
3890
- })
3891
- ]
3892
- }), !collapsed ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [skills.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillRow, {
3893
- skill,
3894
- hub
3895
- }, skill.name)), disabledMembers.map((record) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DisabledRow, {
3896
- record,
3897
- busy: busyNames.has(record.name),
3898
- duplicate: catalog?.duplicateNames?.includes(record.name) === true,
3899
- onEnable: () => {
3900
- enableDisabled(record);
3901
- },
3902
- onOpen: () => {
3903
- hub.openDetail(record.name);
3904
- }
3905
- }, record.name))] }) : null]
3906
- }, "col:" + collection.name);
3907
- }
3908
- if (topKey === "uncategorized-source" && hasPersonal) {
3909
- const uncategorized = filterBySource(sorted, sourceFilter, origins).filter((skill) => origins[skill.name] === void 0 && !isProjectSource(skill.source));
3910
- const personalDisabled = (catalog?.disabled ?? []).filter((record) => origins[record.name] === void 0).filter((record) => normalized.length === 0 || record.name.toLocaleLowerCase().includes(normalized) || record.description.toLocaleLowerCase().includes(normalized)).filter((record) => sourceFilter === "all" || sourceFilter === "private");
3911
- const allPersonalNames = [...uncategorized.map((s) => s.name), ...personalDisabled.map((r) => r.name)];
3912
- if (allPersonalNames.length === 0) return null;
3913
- const collapsed = collapsedGroups.has("uncategorized-source");
3914
- const isDragging = topDragKey === "uncategorized-source";
3915
- const isOver = topOverKey === "uncategorized-source" && topDragKey !== "uncategorized-source";
3916
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3917
- className: panel_module_css_default.section + (isDragging ? " " + panel_module_css_default.dragging : "") + (isOver ? " " + panel_module_css_default.dragOver : ""),
3918
- draggable: true,
3919
- onDragStart: (e) => {
3920
- setTopDragKey("uncategorized-source");
3921
- e.dataTransfer.effectAllowed = "move";
3922
- e.dataTransfer.setData("text/plain", "uncategorized-source");
3923
- },
3924
- onDragOver: (e) => {
3925
- e.preventDefault();
3926
- if (topOverKey !== "uncategorized-source") setTopOverKey("uncategorized-source");
3927
- },
3928
- onDragLeave: () => {
3929
- if (topOverKey === "uncategorized-source") setTopOverKey(null);
3930
- },
3931
- onDrop: (e) => {
3932
- e.preventDefault();
3933
- handleTopDrop("uncategorized-source");
3934
- setTopOverKey(null);
3935
- },
3936
- onDragEnd: () => {
3937
- setTopDragKey(null);
3938
- setTopOverKey(null);
3939
- },
3940
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3941
- className: panel_module_css_default.groupHead,
3942
- children: [
3943
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3944
- className: panel_module_css_default.dragHandle,
3945
- "aria-hidden": true,
3946
- title: "拖拽调整顺序",
3947
- children: "⋮⋮"
3948
- }),
3949
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3950
- type: "button",
3951
- className: panel_module_css_default.disclosure,
3952
- "aria-expanded": !collapsed,
3953
- onClick: () => {
3954
- toggleGroupCollapse("uncategorized-source");
3955
- },
3956
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.chevron + (collapsed ? " " + panel_module_css_default.chevronCollapsed : "") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3957
- className: panel_module_css_default.groupTitle,
3958
- children: [
3959
- tt("groups.personal"),
3960
- " · ",
3961
- allPersonalNames.length,
3962
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupSummary, {
3963
- members: allPersonalNames,
3964
- hub
3965
- })
3966
- ]
3967
- })]
3968
- }),
3969
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3970
- className: panel_module_css_default.groupOps,
3971
- children: hub.editMode ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3972
- type: "button",
3973
- className: panel_module_css_default.opBtn + " " + panel_module_css_default.opDanger,
3974
- title: tt("source.deleteGroupHint", { count: allPersonalNames.length }),
3975
- onClick: (event) => {
3976
- event.stopPropagation();
3977
- requestDeleteGroup(tt("groups.personal"), allPersonalNames);
3978
- },
3979
- children: tt("source.deleteGroup")
3980
- }) : null
3981
- })
3982
- ]
3983
- }), !collapsed ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [uncategorized.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillRow, {
3984
- skill,
3985
- hub
3986
- }, skill.name)), personalDisabled.map((record) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DisabledRow, {
3987
- record,
3988
- busy: busyNames.has(record.name),
3989
- duplicate: catalog?.duplicateNames?.includes(record.name) === true,
3990
- onEnable: () => {
3991
- enableDisabled(record);
3992
- },
3993
- onOpen: () => {
3994
- hub.openDetail(record.name);
3995
- }
3996
- }, record.name))] }) : null]
3997
- }, "uncategorized-source");
3998
- }
3999
- return null;
4000
- })] });
4024
+ })] }) : null
4025
+ })
4026
+ ]
4027
+ }), !collapsed ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [uncategorized.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillRow, {
4028
+ skill,
4029
+ ...rowProps
4030
+ }, skill.name)), personalDisabled.map((record) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DisabledRow, {
4031
+ record,
4032
+ busy: busyNames.has(record.name),
4033
+ duplicate: duplicateNames.has(record.name),
4034
+ onEnable: () => {
4035
+ enableDisabled(record);
4036
+ },
4037
+ onOpen: () => {
4038
+ hub.openDetail(record.name);
4039
+ }
4040
+ }, record.name))] }) : null]
4041
+ }, "uncategorized-source");
4042
+ }
4043
+ return null;
4044
+ })
4045
+ ] });
4001
4046
  }
4002
4047
  //#endregion
4003
4048
  //#region src/client/panel/ScenesView.tsx
@@ -4009,9 +4054,11 @@ window.__ModuleLoader__.load({
4009
4054
  */
4010
4055
  function ScenesView(props) {
4011
4056
  const { hub } = props;
4012
- const { catalog, groupsState, sorted, normalized, collapsedGroups, viewNames, actionNames, batchBusy, busyNames, newTagName, setNewTagName, tagBusy, createTag, toggleGroupCollapse, toggleGroup, setEditingTag, setEditName, setMembersDraft, setEditSearch, enableDisabled } = hub;
4057
+ const { catalog, groupsState, sorted, normalized, collapsedGroups, viewNames, actionNames, batchBusy, busyNames, newTagName, setNewTagName, tagBusy, createTag, toggleGroupCollapse, setAllGroupsCollapsed, toggleGroup, setEditingTag, setEditName, setMembersDraft, setEditSearch, enableDisabled } = hub;
4013
4058
  const [dragId, setDragId] = (0, react.useState)(null);
4014
4059
  const [overId, setOverId] = (0, react.useState)(null);
4060
+ /** 重复技能名集合:整表只建一次,行内用 has 取代逐行线性 includes。 */
4061
+ const duplicateNames = (0, react.useMemo)(() => new Set(catalog?.duplicateNames ?? []), [catalog]);
4015
4062
  const handleDrop = (targetId) => {
4016
4063
  if (dragId === null || dragId === targetId || groupsState === null) return;
4017
4064
  const ids = groupsState.tags.map((t) => t.id);
@@ -4023,6 +4070,38 @@ window.__ModuleLoader__.load({
4023
4070
  next.splice(to, 0, moved);
4024
4071
  hub.reorderTags(next);
4025
4072
  };
4073
+ const drag = useDragReorder({
4074
+ dragKey: dragId,
4075
+ overKey: overId,
4076
+ setDragKey: setDragId,
4077
+ setOverKey: setOverId,
4078
+ onDrop: handleDrop
4079
+ });
4080
+ const tagKeys = (groupsState?.tags ?? []).map((tag) => "tag:" + tag.id);
4081
+ /** 所有场景是否已折叠(决定「全部折叠/展开」按钮的文案)。 */
4082
+ const allTagsCollapsed = tagKeys.length > 0 && tagKeys.every((key) => collapsedGroups.has(key));
4083
+ /** 键盘可用的排序:与拖拽同一条 store 路径。 */
4084
+ const moveTag = (index, direction) => {
4085
+ const ids = (groupsState?.tags ?? []).map((tag) => tag.id);
4086
+ const to = index + direction;
4087
+ if (to < 0 || to >= ids.length) return;
4088
+ const next = [...ids];
4089
+ const [moved] = next.splice(index, 1);
4090
+ next.splice(to, 0, moved);
4091
+ hub.reorderTags(next);
4092
+ };
4093
+ /** SkillRow 收窄后的 props:父组件统一传入它实际消费的字段。 */
4094
+ const rowProps = {
4095
+ uses: hub.uses,
4096
+ hubConfig: hub.hubConfig,
4097
+ busyNames,
4098
+ editMode: hub.editMode,
4099
+ tagBusy,
4100
+ duplicateNames,
4101
+ toggle: hub.toggle,
4102
+ openDetail: hub.openDetail,
4103
+ requestDeleteSkill: hub.requestDeleteSkill
4104
+ };
4026
4105
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4027
4106
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("form", {
4028
4107
  className: panel_module_css_default.form,
@@ -4050,45 +4129,32 @@ window.__ModuleLoader__.load({
4050
4129
  className: panel_module_css_default.empty,
4051
4130
  children: tt("groups.empty")
4052
4131
  }) : null,
4053
- groupsState?.tags.map((tag) => {
4132
+ tagKeys.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4133
+ className: panel_module_css_default.listTools,
4134
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4135
+ type: "button",
4136
+ className: panel_module_css_default.opBtn,
4137
+ onClick: () => {
4138
+ setAllGroupsCollapsed(allTagsCollapsed ? null : tagKeys);
4139
+ },
4140
+ children: allTagsCollapsed ? tt("groups.expandAll") : tt("groups.collapseAll")
4141
+ })
4142
+ }) : null,
4143
+ groupsState?.tags.map((tag, index) => {
4054
4144
  const skills = sorted.filter((skill) => tag.skillNames.includes(skill.name));
4055
4145
  const disabledMembers = (catalog?.disabled ?? []).filter((record) => tag.skillNames.includes(record.name) && (normalized.length === 0 || record.name.toLocaleLowerCase().includes(normalized) || record.description.toLocaleLowerCase().includes(normalized)));
4056
4146
  const collapsed = collapsedGroups.has("tag:" + tag.id);
4057
4147
  const view = groupSwitchView(tag.skillNames, viewNames);
4058
4148
  const hasWritable = tag.skillNames.some((name) => actionNames.has(name));
4059
- const isDragging = dragId === tag.id;
4060
- const isOver = overId === tag.id && dragId !== tag.id;
4061
4149
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4062
- className: panel_module_css_default.section + (isDragging ? " " + panel_module_css_default.dragging : "") + (isOver ? " " + panel_module_css_default.dragOver : ""),
4063
- draggable: true,
4064
- onDragStart: (e) => {
4065
- setDragId(tag.id);
4066
- e.dataTransfer.effectAllowed = "move";
4067
- e.dataTransfer.setData("text/plain", tag.id);
4068
- },
4069
- onDragOver: (e) => {
4070
- e.preventDefault();
4071
- if (overId !== tag.id) setOverId(tag.id);
4072
- },
4073
- onDragLeave: () => {
4074
- if (overId === tag.id) setOverId(null);
4075
- },
4076
- onDrop: (e) => {
4077
- e.preventDefault();
4078
- handleDrop(tag.id);
4079
- setOverId(null);
4080
- },
4081
- onDragEnd: () => {
4082
- setDragId(null);
4083
- setOverId(null);
4084
- },
4150
+ ...drag(tag.id),
4085
4151
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4086
4152
  className: panel_module_css_default.groupHead,
4087
4153
  children: [
4088
4154
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4089
4155
  className: panel_module_css_default.dragHandle,
4090
4156
  "aria-hidden": true,
4091
- title: "拖拽调整顺序",
4157
+ title: tt("drag.reorder"),
4092
4158
  children: "⋮⋮"
4093
4159
  }),
4094
4160
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
@@ -4106,46 +4172,53 @@ window.__ModuleLoader__.load({
4106
4172
  tag.skillNames.length,
4107
4173
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupSummary, {
4108
4174
  members: tag.skillNames,
4109
- hub
4175
+ uses: hub.uses,
4176
+ hubConfig: hub.hubConfig
4110
4177
  })
4111
4178
  ]
4112
4179
  })]
4113
4180
  }),
4114
4181
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4115
4182
  className: panel_module_css_default.groupOps,
4116
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4117
- type: "button",
4118
- role: "switch",
4119
- "aria-checked": view.state !== "off",
4120
- "aria-label": tag.name,
4121
- className: panel_module_css_default.switch + (view.state === "on" ? " " + panel_module_css_default.switchOn : view.state === "mixed" ? " " + panel_module_css_default.switchMixed : ""),
4122
- disabled: batchBusy || tag.skillNames.length === 0 || view.state !== "off" && !hasWritable,
4123
- title: view.state !== "off" && !hasWritable ? tt("groups.noWritable") : void 0,
4124
- onClick: (event) => {
4125
- event.stopPropagation();
4126
- toggleGroup("tag:" + tag.id, tag.name, view.state);
4127
- },
4128
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: panel_module_css_default.switchThumb })
4129
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4130
- type: "button",
4131
- className: panel_module_css_default.opBtn,
4132
- onClick: () => {
4133
- setEditingTag(tag);
4134
- setEditName(tag.name);
4135
- setMembersDraft(new Set(tag.skillNames));
4136
- setEditSearch("");
4137
- },
4138
- children: tt("groups.edit")
4139
- })]
4183
+ children: [
4184
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupSwitchButton, {
4185
+ state: view.state,
4186
+ label: tag.name,
4187
+ memberCount: tag.skillNames.length,
4188
+ batchBusy,
4189
+ hasWritable,
4190
+ onToggle: () => {
4191
+ toggleGroup("tag:" + tag.id, tag.name, view.state);
4192
+ }
4193
+ }),
4194
+ hub.editMode ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReorderButtons, {
4195
+ canMoveUp: index > 0,
4196
+ canMoveDown: index < (groupsState?.tags.length ?? 1) - 1,
4197
+ onMove: (direction) => {
4198
+ moveTag(index, direction);
4199
+ }
4200
+ }) : null,
4201
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4202
+ type: "button",
4203
+ className: panel_module_css_default.opBtn,
4204
+ onClick: () => {
4205
+ setEditingTag(tag);
4206
+ setEditName(tag.name);
4207
+ setMembersDraft(new Set(tag.skillNames));
4208
+ setEditSearch("");
4209
+ },
4210
+ children: tt("groups.edit")
4211
+ })
4212
+ ]
4140
4213
  })
4141
4214
  ]
4142
4215
  }), !collapsed ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [skills.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillRow, {
4143
4216
  skill,
4144
- hub
4217
+ ...rowProps
4145
4218
  }, skill.name)), disabledMembers.map((record) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DisabledRow, {
4146
4219
  record,
4147
4220
  busy: busyNames.has(record.name),
4148
- duplicate: hub.catalog?.duplicateNames?.includes(record.name) === true,
4221
+ duplicate: duplicateNames.has(record.name),
4149
4222
  onEnable: () => {
4150
4223
  enableDisabled(record);
4151
4224
  },
@@ -4613,7 +4686,7 @@ window.__ModuleLoader__.load({
4613
4686
  type: "button",
4614
4687
  className: panel_module_css_default.opBtn + (updateDanger ? " " + panel_module_css_default.opDanger : ""),
4615
4688
  disabled: updateDisabled,
4616
- title: hasUpdate ? "将同步到上游最新版并提示可更新的本地技能" : void 0,
4689
+ title: hasUpdate ? tt("market.syncHint") : void 0,
4617
4690
  onClick: updateAction,
4618
4691
  children: updateLabel
4619
4692
  }),
@@ -4642,6 +4715,15 @@ window.__ModuleLoader__.load({
4642
4715
  * 勾选导入+进度+结果)都包在这里。搜索/过滤/分页是卡片级本地状态,
4643
4716
  * 随卡片挂载而生、关闭而灭。从 MarketView.tsx 原样搬出,渲染逻辑不变。
4644
4717
  */
4718
+ /**
4719
+ * 已知技能根的显示名键;未知根直接显示目录名。
4720
+ * zh 字典里这两项就是目录名本身(与接线前的硬编码文本一致)。
4721
+ */
4722
+ function rootLabelKey(root) {
4723
+ if (root === "skills") return "repo.root.skills";
4724
+ if (root === "design-templates") return "repo.root.designTemplates";
4725
+ return "repo.root.generic";
4726
+ }
4645
4727
  function RepoScanCard(props) {
4646
4728
  const { hub } = props;
4647
4729
  const { repoDiscoverState, scanningRepo, repoResult, repoSelected, setRepoSelected, repoImporting, toggleRepoSelected, importRepo, cancelImport } = hub;
@@ -4672,7 +4754,7 @@ window.__ModuleLoader__.load({
4672
4754
  alignItems: "center",
4673
4755
  gap: 6
4674
4756
  },
4675
- children: ["扫描结果", /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4757
+ children: [tt("repo.scanTitle"), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4676
4758
  style: {
4677
4759
  fontWeight: 400,
4678
4760
  opacity: .75
@@ -4695,7 +4777,7 @@ window.__ModuleLoader__.load({
4695
4777
  onClick: () => {
4696
4778
  hub.clearScan();
4697
4779
  },
4698
- children: "关闭"
4780
+ children: tt("repo.close")
4699
4781
  })]
4700
4782
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4701
4783
  style: { padding: "0 12px 12px" },
@@ -4773,9 +4855,9 @@ window.__ModuleLoader__.load({
4773
4855
  },
4774
4856
  value: repoSearch,
4775
4857
  onChange: (e) => setRepoSearch(e.target.value),
4776
- placeholder: "搜索技能名…"
4858
+ placeholder: tt("repo.searchPlaceholder")
4777
4859
  }),
4778
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4860
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4779
4861
  type: "button",
4780
4862
  className: panel_module_css_default.button + (repoFilter === "all" ? " " + panel_module_css_default.primary : ""),
4781
4863
  style: {
@@ -4783,7 +4865,7 @@ window.__ModuleLoader__.load({
4783
4865
  fontSize: 12
4784
4866
  },
4785
4867
  onClick: () => setRepoFilter("all"),
4786
- children: ["全部 ", entries.length]
4868
+ children: tt("repo.allRoots", { count: entries.length })
4787
4869
  }),
4788
4870
  roots.map((root) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4789
4871
  type: "button",
@@ -4794,7 +4876,7 @@ window.__ModuleLoader__.load({
4794
4876
  },
4795
4877
  onClick: () => setRepoFilter(root),
4796
4878
  children: [
4797
- root,
4879
+ tt(rootLabelKey(root), { root }),
4798
4880
  " ",
4799
4881
  counts.get(root) ?? 0
4800
4882
  ]
@@ -4810,19 +4892,13 @@ window.__ModuleLoader__.load({
4810
4892
  flexWrap: "wrap",
4811
4893
  gap: 8
4812
4894
  },
4813
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
4814
- "已选 ",
4815
- selected.length,
4816
- "/",
4817
- entries.length,
4818
- " · ",
4819
- formatBytes(selectedBytes),
4820
- " · 显示 ",
4821
- paged.length,
4822
- "/",
4823
- filtered.length,
4824
- "(过滤后)"
4825
- ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4895
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tt("repo.selection", {
4896
+ selected: selected.length,
4897
+ total: entries.length,
4898
+ size: formatBytes(selectedBytes),
4899
+ shown: paged.length,
4900
+ filtered: filtered.length
4901
+ }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4826
4902
  style: {
4827
4903
  display: "flex",
4828
4904
  gap: 6
@@ -4838,9 +4914,9 @@ window.__ModuleLoader__.load({
4838
4914
  onClick: () => {
4839
4915
  setRepoSelected(new Set(filtered.filter((e) => !isExisting(e)).slice(0, visibleCount).map((e) => e.path)));
4840
4916
  },
4841
- children: "全选当前显示"
4917
+ children: tt("repo.selectVisible")
4842
4918
  }),
4843
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4919
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4844
4920
  type: "button",
4845
4921
  className: panel_module_css_default.button,
4846
4922
  style: {
@@ -4850,7 +4926,7 @@ window.__ModuleLoader__.load({
4850
4926
  onClick: () => {
4851
4927
  setRepoSelected(new Set(entries.filter((e) => !isExisting(e)).map((e) => e.path)));
4852
4928
  },
4853
- children: ["全选全部 ", entries.filter((e) => !isExisting(e)).length]
4929
+ children: tt("repo.selectAllN", { count: entries.filter((e) => !isExisting(e)).length })
4854
4930
  }),
4855
4931
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4856
4932
  type: "button",
@@ -4872,7 +4948,7 @@ window.__ModuleLoader__.load({
4872
4948
  children: paged.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4873
4949
  className: panel_module_css_default.empty,
4874
4950
  style: { padding: 20 },
4875
- children: "无匹配技能"
4951
+ children: tt("repo.noMatch")
4876
4952
  }) : paged.map((entry) => {
4877
4953
  const existing = isExisting(entry);
4878
4954
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
@@ -4919,15 +4995,14 @@ window.__ModuleLoader__.load({
4919
4995
  textAlign: "center",
4920
4996
  marginTop: 8
4921
4997
  },
4922
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4998
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4923
4999
  type: "button",
4924
5000
  className: panel_module_css_default.button,
4925
5001
  onClick: () => setVisibleCount((n) => n + 50),
4926
- children: [
4927
- "加载更多 50 (剩余 ",
4928
- filtered.length - visibleCount,
4929
- ")"
4930
- ]
5002
+ children: tt("repo.loadMore", {
5003
+ step: 50,
5004
+ remaining: filtered.length - visibleCount
5005
+ })
4931
5006
  })
4932
5007
  }) : null,
4933
5008
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -4986,10 +5061,10 @@ window.__ModuleLoader__.load({
4986
5061
  formatBytes(repoResult.totalBytes),
4987
5062
  speed !== "" ? ` · ${speed}` : "",
4988
5063
  " · ",
4989
- repoResult.done,
4990
- "/",
4991
- repoResult.total,
4992
- " 个技能"
5064
+ tt("repo.skillProgress", {
5065
+ done: repoResult.done,
5066
+ total: repoResult.total
5067
+ })
4993
5068
  ]
4994
5069
  }),
4995
5070
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -5196,24 +5271,175 @@ window.__ModuleLoader__.load({
5196
5271
  ] });
5197
5272
  }
5198
5273
  //#endregion
5199
- //#region src/client/panel/hooks/shared.ts
5200
- /** localStorage key of the last daily auto-check timestamp (survives sessions). */
5201
- const AUTO_CHECK_KEY = "skill-hub.last-auto-check";
5202
- /** Daily auto-check interval: at most one network round per day. */
5203
- const AUTO_CHECK_INTERVAL_MS = 1440 * 6e4;
5204
- /** Whether the daily auto-check should run now (first visit or ≥24h since the last). */
5205
- function shouldAutoCheck() {
5206
- try {
5207
- const raw = localStorage.getItem(AUTO_CHECK_KEY);
5208
- const last = raw === null ? NaN : Number(raw);
5209
- if (!Number.isFinite(last)) return true;
5210
- return Date.now() - last >= AUTO_CHECK_INTERVAL_MS;
5211
- } catch {
5212
- return true;
5213
- }
5214
- }
5215
- /** Record that the daily auto-check ran. */
5216
- function markAutoChecked() {
5274
+ //#region src/client/panel/PanelDialogs.tsx
5275
+ function PanelDialogs(props) {
5276
+ const { conflictDialog, tags, collections, setConflictDialog, resolveConflict, confirmDialog, setConfirmDialog, runConfirmed, branchChoice, branchBusy, setBranchChoice, confirmBranchChoice, versionDialog, versionBusy, setVersionDialog, confirmVersionDialog, marketSyncDialog, syncBusy, setMarketSyncDialog, confirmMarketSync, deleteSkillDialog, setDeleteSkillDialog, runDeleteSkill, deleteGroupDialog, setDeleteGroupDialog, runDeleteGroup, confirmClearTrash, setConfirmClearTrash, clearTrash } = props;
5277
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
5278
+ conflictDialog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConflictDialog, {
5279
+ dialog: conflictDialog,
5280
+ tags,
5281
+ collections,
5282
+ onClose: () => {
5283
+ setConflictDialog(null);
5284
+ },
5285
+ onKeepOn: () => {
5286
+ resolveConflict(false);
5287
+ },
5288
+ onCloseAll: () => {
5289
+ resolveConflict(true);
5290
+ }
5291
+ }) : null,
5292
+ confirmDialog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmDialog, {
5293
+ title: confirmDialog.kind === "sync" ? tt("source.syncConfirmTitle") : tt("source.deleteConfirmTitle"),
5294
+ text: confirmDialog.kind === "sync" ? tt("source.syncConfirmText") : tt("source.deleteConfirmText"),
5295
+ items: confirmDialog.skills,
5296
+ confirmLabel: confirmDialog.kind === "sync" ? tt("source.sync") : tt("source.followDelete"),
5297
+ danger: confirmDialog.kind === "delete",
5298
+ onCancel: () => {
5299
+ setConfirmDialog(null);
5300
+ },
5301
+ onConfirm: () => {
5302
+ runConfirmed();
5303
+ }
5304
+ }) : null,
5305
+ branchChoice !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BranchChoiceDialog, {
5306
+ choice: branchChoice,
5307
+ busy: branchBusy,
5308
+ onSelect: (selected) => {
5309
+ setBranchChoice({
5310
+ ...branchChoice,
5311
+ selected
5312
+ });
5313
+ },
5314
+ onCancel: () => {
5315
+ setBranchChoice(null);
5316
+ },
5317
+ onConfirm: () => {
5318
+ confirmBranchChoice();
5319
+ }
5320
+ }) : null,
5321
+ versionDialog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VersionChoiceDialog, {
5322
+ choice: versionDialog,
5323
+ busy: versionBusy,
5324
+ onSelect: (selected) => {
5325
+ setVersionDialog({
5326
+ ...versionDialog,
5327
+ selected
5328
+ });
5329
+ },
5330
+ onCustom: (custom) => {
5331
+ setVersionDialog({
5332
+ ...versionDialog,
5333
+ custom
5334
+ });
5335
+ },
5336
+ onCancel: () => {
5337
+ setVersionDialog(null);
5338
+ },
5339
+ onConfirm: () => {
5340
+ confirmVersionDialog();
5341
+ }
5342
+ }) : null,
5343
+ marketSyncDialog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarketSyncDialog, {
5344
+ dialog: marketSyncDialog,
5345
+ busy: syncBusy,
5346
+ onToggle: (name, checked) => {
5347
+ const next = new Set(marketSyncDialog.selected);
5348
+ if (checked) next.add(name);
5349
+ else next.delete(name);
5350
+ setMarketSyncDialog({
5351
+ ...marketSyncDialog,
5352
+ selected: next
5353
+ });
5354
+ },
5355
+ onCancel: () => {
5356
+ setMarketSyncDialog(null);
5357
+ },
5358
+ onConfirm: () => {
5359
+ confirmMarketSync();
5360
+ }
5361
+ }) : null,
5362
+ deleteSkillDialog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmDialog, {
5363
+ title: tt("delete.confirmTitle"),
5364
+ text: tt("delete.confirmText", { name: deleteSkillDialog }),
5365
+ confirmLabel: tt("delete.confirm"),
5366
+ danger: true,
5367
+ onCancel: () => {
5368
+ setDeleteSkillDialog(null);
5369
+ },
5370
+ onConfirm: () => {
5371
+ runDeleteSkill();
5372
+ }
5373
+ }) : null,
5374
+ deleteGroupDialog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmDialog, {
5375
+ title: tt("source.deleteGroupTitle"),
5376
+ text: tt("source.deleteGroupText", {
5377
+ name: deleteGroupDialog.name,
5378
+ count: deleteGroupDialog.skillNames.length
5379
+ }),
5380
+ items: deleteGroupDialog.skillNames,
5381
+ confirmLabel: tt("source.deleteGroup"),
5382
+ danger: true,
5383
+ onCancel: () => {
5384
+ setDeleteGroupDialog(null);
5385
+ },
5386
+ onConfirm: () => {
5387
+ runDeleteGroup();
5388
+ }
5389
+ }) : null,
5390
+ confirmClearTrash ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmDialog, {
5391
+ title: tt("source.clearTrashConfirmTitle"),
5392
+ text: tt("source.clearTrashConfirmText"),
5393
+ confirmLabel: tt("source.clearTrashConfirm"),
5394
+ danger: true,
5395
+ onCancel: () => {
5396
+ setConfirmClearTrash(false);
5397
+ },
5398
+ onConfirm: () => {
5399
+ clearTrash();
5400
+ }
5401
+ }) : null
5402
+ ] });
5403
+ }
5404
+ //#endregion
5405
+ //#region src/client/panel/hooks/shared.ts
5406
+ /**
5407
+ * 统一异步动作外壳:清错 → 执行 → 异常转错误条幅 → 收尾(忙碌开关)。
5408
+ * 只用于同形的 clearFail/try/catch/finally 站点;成功后清错、自定义收尾、
5409
+ * 需要返回值的站点保持内联。
5410
+ */
5411
+ async function runFlow(shared, body, settle = () => {}) {
5412
+ shared.clearFail();
5413
+ try {
5414
+ return await body();
5415
+ } catch (error) {
5416
+ shared.fail(errorMessage(error));
5417
+ return;
5418
+ } finally {
5419
+ settle();
5420
+ }
5421
+ }
5422
+ /** Catalog poll interval while the panel is mounted (the provider watcher feeds this). */
5423
+ const POLL_MS = 5e3;
5424
+ /** Relaxed poll for slower-changing data (stats, groups, sources, config). */
5425
+ const SLOW_POLL_MS = 6e4;
5426
+ /** localStorage key of the last daily auto-check timestamp (survives sessions). */
5427
+ const AUTO_CHECK_KEY = "skill-hub.last-auto-check";
5428
+ /** Daily auto-check interval: at most one network round per day. */
5429
+ const AUTO_CHECK_INTERVAL_MS = 1440 * 6e4;
5430
+ /** Whether the daily auto-check should run now (first visit or ≥24h since the last). */
5431
+ function shouldAutoCheck() {
5432
+ try {
5433
+ const raw = localStorage.getItem(AUTO_CHECK_KEY);
5434
+ const last = raw === null ? NaN : Number(raw);
5435
+ if (!Number.isFinite(last)) return true;
5436
+ return Date.now() - last >= AUTO_CHECK_INTERVAL_MS;
5437
+ } catch {
5438
+ return true;
5439
+ }
5440
+ }
5441
+ /** Record that the daily auto-check ran. */
5442
+ function markAutoChecked() {
5217
5443
  try {
5218
5444
  localStorage.setItem(AUTO_CHECK_KEY, String(Date.now()));
5219
5445
  } catch {}
@@ -5263,7 +5489,7 @@ window.__ModuleLoader__.load({
5263
5489
  usesCatchUp.current += 1;
5264
5490
  loadUses();
5265
5491
  if (usesCatchUp.current >= 72) window.clearInterval(timer);
5266
- }, 5e3);
5492
+ }, POLL_MS);
5267
5493
  return () => {
5268
5494
  window.clearInterval(timer);
5269
5495
  };
@@ -5325,14 +5551,9 @@ window.__ModuleLoader__.load({
5325
5551
  ]);
5326
5552
  const openDetail = (0, react.useCallback)(async (name) => {
5327
5553
  setDetailLoading(true);
5328
- shared.clearFail();
5329
- try {
5554
+ await runFlow(shared, async () => {
5330
5555
  setDetail(await api.skill(name, workspace !== "" ? { cwd: workspace } : void 0));
5331
- } catch (error) {
5332
- shared.fail(errorMessage(error));
5333
- } finally {
5334
- setDetailLoading(false);
5335
- }
5556
+ }, () => setDetailLoading(false));
5336
5557
  }, [
5337
5558
  api,
5338
5559
  workspace,
@@ -5340,68 +5561,54 @@ window.__ModuleLoader__.load({
5340
5561
  ]);
5341
5562
  const toggle = (0, react.useCallback)(async (skill, enabled) => {
5342
5563
  setBusyNames((previous) => new Set(previous).add(skill.name));
5343
- shared.clearFail();
5344
- try {
5564
+ await runFlow(shared, async () => {
5345
5565
  const next = await api.toggle(skill.name, enabled);
5346
5566
  setCatalog(next);
5347
- } catch (error) {
5348
- shared.fail(errorMessage(error));
5349
- } finally {
5567
+ }, () => {
5350
5568
  setBusyNames((previous) => {
5351
5569
  const next = new Set(previous);
5352
5570
  next.delete(skill.name);
5353
5571
  return next;
5354
5572
  });
5355
- }
5573
+ });
5356
5574
  }, [api, shared]);
5357
5575
  const enableDisabled = (0, react.useCallback)(async (record) => {
5358
5576
  setBusyNames((previous) => new Set(previous).add(record.name));
5359
- shared.clearFail();
5360
- try {
5577
+ await runFlow(shared, async () => {
5361
5578
  const next = await api.toggle(record.name, true);
5362
5579
  setCatalog(next);
5363
- } catch (error) {
5364
- shared.fail(errorMessage(error));
5365
- } finally {
5580
+ }, () => {
5366
5581
  setBusyNames((previous) => {
5367
5582
  const next = new Set(previous);
5368
5583
  next.delete(record.name);
5369
5584
  return next;
5370
5585
  });
5371
- }
5586
+ });
5372
5587
  }, [api, shared]);
5373
5588
  /** Toggle an explicit name set in one write (enables disabled members too). */
5374
5589
  const batchToggleNames = (0, react.useCallback)(async (names, enabled) => {
5375
5590
  if (names.length === 0) return;
5376
5591
  shared.setBatchBusy(true);
5377
- shared.clearFail();
5378
- try {
5592
+ await runFlow(shared, async () => {
5379
5593
  const next = await api.toggleBatch(names, enabled);
5380
5594
  setCatalog(next.catalog);
5381
5595
  if (next.failures.length > 0) shared.fail("toggle-batch: " + next.failures.map((failure) => failure.name + ": " + failure.error).join("; "));
5382
- } catch (error) {
5383
- shared.fail(errorMessage(error));
5384
- } finally {
5385
- shared.setBatchBusy(false);
5386
- }
5596
+ }, () => shared.setBatchBusy(false));
5387
5597
  }, [api, shared]);
5388
5598
  const fixDiagnostic = (0, react.useCallback)(async (path) => {
5389
5599
  setFixingPaths((previous) => new Set(previous).add(path));
5390
- shared.clearFail();
5391
- try {
5600
+ await runFlow(shared, async () => {
5392
5601
  if (!window.confirm(tt("diag.fixConfirm", { path }))) return;
5393
5602
  await api.fixDiagnostic(path);
5394
5603
  await load();
5395
5604
  shared.succeed(tt("diag.fixed"));
5396
- } catch (error) {
5397
- shared.fail(errorMessage(error));
5398
- } finally {
5605
+ }, () => {
5399
5606
  setFixingPaths((previous) => {
5400
5607
  const next = new Set(previous);
5401
5608
  next.delete(path);
5402
5609
  return next;
5403
5610
  });
5404
- }
5611
+ });
5405
5612
  }, [
5406
5613
  api,
5407
5614
  load,
@@ -5603,15 +5810,10 @@ window.__ModuleLoader__.load({
5603
5810
  const name = newTagName.trim();
5604
5811
  if (name === "") return;
5605
5812
  shared.setTagBusy(true);
5606
- shared.clearFail();
5607
- try {
5813
+ await runFlow(shared, async () => {
5608
5814
  applyTags(await api.saveTag({ name }));
5609
5815
  setNewTagName("");
5610
- } catch (error) {
5611
- shared.fail(errorMessage(error));
5612
- } finally {
5613
- shared.setTagBusy(false);
5614
- }
5816
+ }, () => shared.setTagBusy(false));
5615
5817
  }, [
5616
5818
  api,
5617
5819
  applyTags,
@@ -5620,15 +5822,10 @@ window.__ModuleLoader__.load({
5620
5822
  ]),
5621
5823
  deleteTag: (0, react.useCallback)(async (id) => {
5622
5824
  shared.setTagBusy(true);
5623
- shared.clearFail();
5624
- try {
5825
+ await runFlow(shared, async () => {
5625
5826
  applyTags(await api.deleteTag(id));
5626
5827
  setEditingTag(null);
5627
- } catch (error) {
5628
- shared.fail(errorMessage(error));
5629
- } finally {
5630
- shared.setTagBusy(false);
5631
- }
5828
+ }, () => shared.setTagBusy(false));
5632
5829
  }, [
5633
5830
  api,
5634
5831
  applyTags,
@@ -5636,8 +5833,7 @@ window.__ModuleLoader__.load({
5636
5833
  ]),
5637
5834
  saveTag: (0, react.useCallback)(async (id, name, memberNames) => {
5638
5835
  shared.setTagBusy(true);
5639
- shared.clearFail();
5640
- try {
5836
+ await runFlow(shared, async () => {
5641
5837
  const safeName = name.trim();
5642
5838
  let tags = null;
5643
5839
  if (safeName !== "") tags = await api.saveTag({
@@ -5648,11 +5844,7 @@ window.__ModuleLoader__.load({
5648
5844
  if (tags === null) return;
5649
5845
  applyTags(tags);
5650
5846
  setEditingTag(null);
5651
- } catch (error) {
5652
- shared.fail(errorMessage(error));
5653
- } finally {
5654
- shared.setTagBusy(false);
5655
- }
5847
+ }, () => shared.setTagBusy(false));
5656
5848
  }, [
5657
5849
  api,
5658
5850
  applyTags,
@@ -5683,39 +5875,6 @@ window.__ModuleLoader__.load({
5683
5875
  groupsState,
5684
5876
  shared
5685
5877
  ]),
5686
- reorderCollections: (0, react.useCallback)(async (orderedNames) => {
5687
- shared.setTagBusy(true);
5688
- shared.clearFail();
5689
- try {
5690
- const collections = await api.reorderCollections(orderedNames);
5691
- setGroupsState((prev) => prev === null ? prev : {
5692
- ...prev,
5693
- collections
5694
- });
5695
- loadGroups();
5696
- } catch (error) {
5697
- const msg = errorMessage(error);
5698
- if (msg.includes("404") || msg.toLowerCase().includes("not found")) {
5699
- setGroupsState((prev) => {
5700
- if (prev === null) return prev;
5701
- const map = new Map(prev.collections.map((c) => [c.name, c]));
5702
- const reordered = orderedNames.map((n) => map.get(n)).filter((c) => c !== void 0);
5703
- for (const c of prev.collections) if (!reordered.some((r) => r.name === c.name)) reordered.push(c);
5704
- return {
5705
- ...prev,
5706
- collections: reordered
5707
- };
5708
- });
5709
- shared.succeed("已临时调整顺序(本地生效,重启宿主后持久化)");
5710
- } else shared.fail(msg);
5711
- } finally {
5712
- shared.setTagBusy(false);
5713
- }
5714
- }, [
5715
- api,
5716
- loadGroups,
5717
- shared
5718
- ]),
5719
5878
  reorderSourceGroups: (0, react.useCallback)(async (orderedKeys) => {
5720
5879
  shared.setTagBusy(true);
5721
5880
  shared.clearFail();
@@ -5789,17 +5948,12 @@ window.__ModuleLoader__.load({
5789
5948
  loadSources,
5790
5949
  checkSources: (0, react.useCallback)(async (repo) => {
5791
5950
  setCheckingSource(repo ?? "all");
5792
- shared.clearFail();
5793
- try {
5951
+ await runFlow(shared, async () => {
5794
5952
  const result = await api.checkSources(repo);
5795
5953
  const next = { ...sourceCheck };
5796
5954
  for (const item of result.results) next[item.repo] = item;
5797
5955
  setSourceCheck(next);
5798
- } catch (error) {
5799
- shared.fail(errorMessage(error));
5800
- } finally {
5801
- setCheckingSource(null);
5802
- }
5956
+ }, () => setCheckingSource(null));
5803
5957
  }, [
5804
5958
  api,
5805
5959
  sourceCheck,
@@ -5825,8 +5979,7 @@ window.__ModuleLoader__.load({
5825
5979
  setConfirmDialog(null);
5826
5980
  if (dialog.kind === "sync") {
5827
5981
  setSyncingSource(dialog.repo);
5828
- shared.clearFail();
5829
- try {
5982
+ await runFlow(shared, async () => {
5830
5983
  const result = await api.syncSource(dialog.repo, dialog.skills);
5831
5984
  await Promise.all([
5832
5985
  reloadCatalog(),
@@ -5834,11 +5987,7 @@ window.__ModuleLoader__.load({
5834
5987
  loadSources()
5835
5988
  ]);
5836
5989
  if (result.failed.length > 0) shared.fail("sync: " + result.failed.map((failure) => failure.name + ": " + failure.error).join("; "));
5837
- } catch (error) {
5838
- shared.fail(errorMessage(error));
5839
- } finally {
5840
- setSyncingSource(null);
5841
- }
5990
+ }, () => setSyncingSource(null));
5842
5991
  } else {
5843
5992
  shared.clearFail();
5844
5993
  try {
@@ -5862,19 +6011,14 @@ window.__ModuleLoader__.load({
5862
6011
  ]),
5863
6012
  restoreTrash: (0, react.useCallback)(async (name) => {
5864
6013
  shared.setTagBusy(true);
5865
- shared.clearFail();
5866
- try {
6014
+ await runFlow(shared, async () => {
5867
6015
  await api.restoreSource(name);
5868
6016
  await Promise.all([
5869
6017
  reloadCatalog(),
5870
6018
  reloadGroups(),
5871
6019
  loadSources()
5872
6020
  ]);
5873
- } catch (error) {
5874
- shared.fail(errorMessage(error));
5875
- } finally {
5876
- shared.setTagBusy(false);
5877
- }
6021
+ }, () => shared.setTagBusy(false));
5878
6022
  }, [
5879
6023
  api,
5880
6024
  reloadCatalog,
@@ -5885,16 +6029,11 @@ window.__ModuleLoader__.load({
5885
6029
  clearTrash: (0, react.useCallback)(async () => {
5886
6030
  setConfirmClearTrash(false);
5887
6031
  shared.setTagBusy(true);
5888
- shared.clearFail();
5889
- try {
6032
+ await runFlow(shared, async () => {
5890
6033
  const result = await api.clearTrash();
5891
6034
  if (result.failed.length > 0) shared.fail("clear trash: " + result.failed.map((failure) => failure.name + ": " + failure.error).join("; "));
5892
6035
  await Promise.all([reloadCatalog(), loadSources()]);
5893
- } catch (error) {
5894
- shared.fail(errorMessage(error));
5895
- } finally {
5896
- shared.setTagBusy(false);
5897
- }
6036
+ }, () => shared.setTagBusy(false));
5898
6037
  }, [
5899
6038
  api,
5900
6039
  reloadCatalog,
@@ -5909,19 +6048,14 @@ window.__ModuleLoader__.load({
5909
6048
  if (name === null) return;
5910
6049
  setDeleteSkillDialog(null);
5911
6050
  shared.setTagBusy(true);
5912
- shared.clearFail();
5913
- try {
6051
+ await runFlow(shared, async () => {
5914
6052
  await api.deleteSkill(name);
5915
6053
  await Promise.all([
5916
6054
  reloadCatalog(),
5917
6055
  reloadGroups(),
5918
6056
  loadSources()
5919
6057
  ]);
5920
- } catch (error) {
5921
- shared.fail(errorMessage(error));
5922
- } finally {
5923
- shared.setTagBusy(false);
5924
- }
6058
+ }, () => shared.setTagBusy(false));
5925
6059
  }, [
5926
6060
  api,
5927
6061
  deleteSkillDialog,
@@ -5969,11 +6103,236 @@ window.__ModuleLoader__.load({
5969
6103
  };
5970
6104
  }
5971
6105
  //#endregion
6106
+ //#region src/client/panel/hooks/useRepoImportFlow.ts
6107
+ /**
6108
+ * useRepoImportFlow — 市场子域:仓库扫描/分支选择/导入任务(轮询+取消)。
6109
+ * 从 useMarketFlow 原样搬出:状态、动作、卸载中断与依赖顺序不变;市场源
6110
+ * 列表刷新(loadMarket)与目录/分组/来源重载经参数注入,不直接碰其他域。
6111
+ */
6112
+ function useRepoImportFlow(api, shared, loadMarket, reloadCatalog, reloadGroups, reloadSources) {
6113
+ const [repoDiscoverState, setRepoDiscoverState] = (0, react.useState)({ status: "idle" });
6114
+ /** Market repo currently being scanned (null when idle); per-row busy state. */
6115
+ const [scanningRepo, setScanningRepo] = (0, react.useState)(null);
6116
+ const [repoSelected, setRepoSelected] = (0, react.useState)(/* @__PURE__ */ new Set());
6117
+ const [repoImporting, setRepoImporting] = (0, react.useState)(false);
6118
+ const [repoResult, setRepoResult] = (0, react.useState)(null);
6119
+ const [importJobId, setImportJobId] = (0, react.useState)(null);
6120
+ const [branchChoice, setBranchChoice] = (0, react.useState)(null);
6121
+ const [branchBusy, setBranchBusy] = (0, react.useState)(false);
6122
+ const pollAbortRef = (0, react.useRef)(null);
6123
+ /** 扫描一个市场源(或手动输入)的仓库。 */
6124
+ const scanRepo = (0, react.useCallback)(async (input) => {
6125
+ const value = input.trim();
6126
+ if (value === "") return;
6127
+ setRepoResult(null);
6128
+ setRepoSelected(/* @__PURE__ */ new Set());
6129
+ setScanningRepo(value);
6130
+ setRepoDiscoverState({ status: "scanning" });
6131
+ shared.clearFail();
6132
+ try {
6133
+ const data = await api.repoDiscover(value);
6134
+ if (data.ref === null) {
6135
+ setBranchChoice({
6136
+ repo: data.repo,
6137
+ branches: data.branches ?? [],
6138
+ selected: (data.branches ?? [])[0] ?? "main"
6139
+ });
6140
+ setRepoDiscoverState({ status: "idle" });
6141
+ return;
6142
+ }
6143
+ setRepoDiscoverState({
6144
+ status: "ready",
6145
+ data
6146
+ });
6147
+ loadMarket();
6148
+ } catch (error) {
6149
+ setRepoDiscoverState({
6150
+ status: "error",
6151
+ message: errorMessage(error)
6152
+ });
6153
+ } finally {
6154
+ setScanningRepo(null);
6155
+ }
6156
+ }, [
6157
+ api,
6158
+ loadMarket,
6159
+ shared
6160
+ ]);
6161
+ /** 确认分支选择:持久化 ref 后重新扫描。 */
6162
+ const confirmBranchChoice = (0, react.useCallback)(async () => {
6163
+ if (branchChoice === null) return;
6164
+ setBranchBusy(true);
6165
+ await runFlow(shared, async () => {
6166
+ await api.setMarketSourceRef(branchChoice.repo, branchChoice.selected);
6167
+ setBranchChoice(null);
6168
+ await scanRepo(branchChoice.repo);
6169
+ }, () => setBranchBusy(false));
6170
+ }, [
6171
+ api,
6172
+ branchChoice,
6173
+ scanRepo,
6174
+ shared
6175
+ ]);
6176
+ /** Toggle one repo preview row. */
6177
+ const toggleRepoSelected = (0, react.useCallback)((path, checked) => {
6178
+ setRepoSelected((previous) => {
6179
+ const next = new Set(previous);
6180
+ if (checked) next.add(path);
6181
+ else next.delete(path);
6182
+ return next;
6183
+ });
6184
+ }, []);
6185
+ /** Import every checked, non-existing repo skill (B方案:job+轮询+进度). */
6186
+ const importRepo = (0, react.useCallback)(async () => {
6187
+ if (repoDiscoverState.status !== "ready") return;
6188
+ setRepoImporting(true);
6189
+ setRepoResult(null);
6190
+ setImportJobId(null);
6191
+ shared.clearFail();
6192
+ let finalProgress = null;
6193
+ try {
6194
+ const created = await api.repoImport(repoDiscoverState.data.repo, [...repoSelected], repoDiscoverState.data.ref ?? void 0);
6195
+ setImportJobId(created.jobId);
6196
+ setRepoResult({
6197
+ ok: true,
6198
+ jobId: created.jobId,
6199
+ status: "running",
6200
+ total: created.total,
6201
+ done: 0,
6202
+ totalBytes: created.totalBytes,
6203
+ downloadedBytes: 0,
6204
+ imported: [],
6205
+ skipped: [],
6206
+ failed: []
6207
+ });
6208
+ pollAbortRef.current?.abort();
6209
+ pollAbortRef.current = new AbortController();
6210
+ const signal = pollAbortRef.current.signal;
6211
+ let attempt = 0;
6212
+ for (;;) {
6213
+ if (signal.aborted) break;
6214
+ const delay = Math.min(2e3, 800 + attempt * 200);
6215
+ await new Promise((r) => setTimeout(r, delay));
6216
+ if (signal.aborted) break;
6217
+ try {
6218
+ const progress = await api.repoImportProgress(created.jobId);
6219
+ setRepoResult(progress);
6220
+ finalProgress = progress;
6221
+ if (progress.status !== "running") break;
6222
+ attempt = 0;
6223
+ } catch (pollError) {
6224
+ if (errorMessage(pollError).includes("not found")) break;
6225
+ attempt += 1;
6226
+ if (attempt > 8) break;
6227
+ }
6228
+ }
6229
+ await Promise.all([
6230
+ reloadCatalog(),
6231
+ loadMarket(),
6232
+ reloadGroups(),
6233
+ reloadSources()
6234
+ ]);
6235
+ } catch (error) {
6236
+ shared.fail(errorMessage(error));
6237
+ } finally {
6238
+ if (finalProgress !== null && (finalProgress.imported.length > 0 || finalProgress.skipped.length > 0)) {
6239
+ const doneNames = /* @__PURE__ */ new Set([...finalProgress.imported.map((r) => r.name), ...finalProgress.skipped.map((r) => r.name)]);
6240
+ const donePaths = new Set(repoDiscoverState.data.entries.filter((e) => doneNames.has(e.name)).map((e) => e.path));
6241
+ setRepoSelected((prev) => {
6242
+ const next = new Set(prev);
6243
+ for (const p of donePaths) next.delete(p);
6244
+ return next;
6245
+ });
6246
+ setRepoDiscoverState((prev) => {
6247
+ if (prev.status !== "ready") return prev;
6248
+ return {
6249
+ ...prev,
6250
+ data: {
6251
+ ...prev.data,
6252
+ entries: prev.data.entries.map((e) => doneNames.has(e.name) ? {
6253
+ ...e,
6254
+ existing: true
6255
+ } : e)
6256
+ }
6257
+ };
6258
+ });
6259
+ }
6260
+ setRepoImporting(false);
6261
+ }
6262
+ }, [
6263
+ api,
6264
+ repoDiscoverState,
6265
+ repoSelected,
6266
+ reloadCatalog,
6267
+ loadMarket,
6268
+ reloadGroups,
6269
+ reloadSources,
6270
+ shared
6271
+ ]);
6272
+ (0, react.useEffect)(() => {
6273
+ return () => {
6274
+ pollAbortRef.current?.abort();
6275
+ };
6276
+ }, []);
6277
+ return {
6278
+ repoDiscoverState,
6279
+ scanningRepo,
6280
+ repoSelected,
6281
+ repoImporting,
6282
+ repoResult,
6283
+ importJobId,
6284
+ branchChoice,
6285
+ branchBusy,
6286
+ setBranchChoice,
6287
+ setRepoSelected,
6288
+ setRepoDiscoverState,
6289
+ setScanningRepo,
6290
+ setRepoResult,
6291
+ scanRepo,
6292
+ confirmBranchChoice,
6293
+ toggleRepoSelected,
6294
+ importRepo,
6295
+ cancelImport: (0, react.useCallback)(async () => {
6296
+ if (importJobId === null) return;
6297
+ pollAbortRef.current?.abort();
6298
+ try {
6299
+ const res = await api.repoImportCancel(importJobId);
6300
+ try {
6301
+ const progress = await api.repoImportProgress(importJobId);
6302
+ setRepoResult(progress);
6303
+ } catch {
6304
+ setRepoResult((prev) => prev !== null ? {
6305
+ ...prev,
6306
+ status: res.status
6307
+ } : prev);
6308
+ }
6309
+ } catch (error) {
6310
+ shared.fail(errorMessage(error));
6311
+ } finally {
6312
+ setRepoImporting(false);
6313
+ }
6314
+ }, [
6315
+ api,
6316
+ importJobId,
6317
+ shared
6318
+ ]),
6319
+ clearScan: (0, react.useCallback)(() => {
6320
+ setRepoDiscoverState({ status: "idle" });
6321
+ setScanningRepo(null);
6322
+ setRepoResult(null);
6323
+ setRepoSelected(/* @__PURE__ */ new Set());
6324
+ setImportJobId(null);
6325
+ pollAbortRef.current?.abort();
6326
+ }, [])
6327
+ };
6328
+ }
6329
+ //#endregion
5972
6330
  //#region src/client/panel/hooks/useMarketFlow.ts
5973
6331
  /**
5974
- * useMarketFlow — 市场域:市场源增删、仓库扫描、分支/版本选择、导入任务
5975
- * (轮询+取消)、更新检查、星标统计、版本同步、全部更新。目录/分组/来源
5976
- * 的刷新与来源检查经聚合根传入,不直接碰其他域的 state。
6332
+ * useMarketFlow — 市场域:市场源增删、更新检查、星标统计、版本同步、全部
6333
+ * 更新;仓库扫描/分支选择/导入任务在 useRepoImportFlow(这里组装它并透传
6334
+ * 同样的返回字段)。目录/分组/来源的刷新与来源检查经聚合根传入,不直接
6335
+ * 碰其他域的 state。
5977
6336
  */
5978
6337
  function useMarketFlow(api, shared, reloadCatalog, reloadGroups, reloadSources, checkSources, sourceCheck) {
5979
6338
  const [marketState, setMarketState] = (0, react.useState)({
@@ -5981,20 +6340,10 @@ window.__ModuleLoader__.load({
5981
6340
  repos: []
5982
6341
  });
5983
6342
  const [marketCheck, setMarketCheck] = (0, react.useState)({});
5984
- const [branchChoice, setBranchChoice] = (0, react.useState)(null);
5985
- const [branchBusy, setBranchBusy] = (0, react.useState)(false);
5986
6343
  const [marketSyncDialog, setMarketSyncDialog] = (0, react.useState)(null);
5987
6344
  const [syncingMarket, setSyncingMarket] = (0, react.useState)(null);
5988
6345
  const [syncBusy, setSyncBusy] = (0, react.useState)(false);
5989
6346
  const [newSourceName, setNewSourceName] = (0, react.useState)("");
5990
- const [repoDiscoverState, setRepoDiscoverState] = (0, react.useState)({ status: "idle" });
5991
- /** Market repo currently being scanned (null when idle); per-row busy state. */
5992
- const [scanningRepo, setScanningRepo] = (0, react.useState)(null);
5993
- const [repoSelected, setRepoSelected] = (0, react.useState)(/* @__PURE__ */ new Set());
5994
- const [repoImporting, setRepoImporting] = (0, react.useState)(false);
5995
- const [repoResult, setRepoResult] = (0, react.useState)(null);
5996
- const [importJobId, setImportJobId] = (0, react.useState)(null);
5997
- const pollAbortRef = (0, react.useRef)(null);
5998
6347
  /** 市场源星星/下载数(SWR 缓存)。 */
5999
6348
  const [marketStats, setMarketStats] = (0, react.useState)({});
6000
6349
  /** 版本对话框:打开时拉 releases + branches,确认后定版并重扫。 */
@@ -6017,6 +6366,7 @@ window.__ModuleLoader__.load({
6017
6366
  shared.fail(errorMessage(error));
6018
6367
  }
6019
6368
  }, [api, shared]);
6369
+ const { repoDiscoverState, scanningRepo, repoSelected, repoImporting, repoResult, importJobId, branchChoice, branchBusy, setBranchChoice, setRepoSelected, setRepoDiscoverState, setScanningRepo, setRepoResult, scanRepo, confirmBranchChoice, toggleRepoSelected, importRepo, cancelImport, clearScan } = useRepoImportFlow(api, shared, loadMarket, reloadCatalog, reloadGroups, reloadSources);
6020
6370
  /** 添加一个市场源(内置市场目录与手动输入共用),并立即扫描它。 */
6021
6371
  const addSource = (0, react.useCallback)(async (input) => {
6022
6372
  const value = input.trim();
@@ -6050,86 +6400,23 @@ window.__ModuleLoader__.load({
6050
6400
  }
6051
6401
  } catch (error) {
6052
6402
  shared.fail(errorMessage(error));
6053
- }
6054
- }, [api, shared]);
6055
- /** 手动输入框的添加动作。 */
6056
- const addMarketSource = (0, react.useCallback)(async () => {
6057
- await addSource(newSourceName);
6058
- }, [addSource, newSourceName]);
6059
- /** 删除一个市场源(不影响已装技能)。 */
6060
- const removeMarketSource = (0, react.useCallback)(async (repo) => {
6061
- shared.setTagBusy(true);
6062
- shared.clearFail();
6063
- try {
6064
- const result = await api.removeMarketSource(repo);
6065
- setMarketState({
6066
- status: "ready",
6067
- repos: result.repos
6068
- });
6069
- } catch (error) {
6070
- shared.fail(errorMessage(error));
6071
- } finally {
6072
- shared.setTagBusy(false);
6073
- }
6074
- }, [api, shared]);
6075
- /** 扫描一个市场源(或手动输入)的仓库。 */
6076
- const scanRepo = (0, react.useCallback)(async (input) => {
6077
- const value = input.trim();
6078
- if (value === "") return;
6079
- setRepoResult(null);
6080
- setRepoSelected(/* @__PURE__ */ new Set());
6081
- setScanningRepo(value);
6082
- setRepoDiscoverState({ status: "scanning" });
6083
- shared.clearFail();
6084
- try {
6085
- const data = await api.repoDiscover(value);
6086
- if (data.ref === null) {
6087
- setBranchChoice({
6088
- repo: data.repo,
6089
- branches: data.branches ?? [],
6090
- selected: (data.branches ?? [])[0] ?? "main"
6091
- });
6092
- setRepoDiscoverState({ status: "idle" });
6093
- return;
6094
- }
6095
- setRepoDiscoverState({
6096
- status: "ready",
6097
- data
6098
- });
6099
- loadMarket();
6100
- } catch (error) {
6101
- setRepoDiscoverState({
6102
- status: "error",
6103
- message: errorMessage(error)
6104
- });
6105
- } finally {
6106
- setScanningRepo(null);
6107
- }
6108
- }, [
6109
- api,
6110
- loadMarket,
6111
- shared
6112
- ]);
6113
- /** 确认分支选择:持久化 ref 后重新扫描。 */
6114
- const confirmBranchChoice = (0, react.useCallback)(async () => {
6115
- if (branchChoice === null) return;
6116
- setBranchBusy(true);
6117
- shared.clearFail();
6118
- try {
6119
- await api.setMarketSourceRef(branchChoice.repo, branchChoice.selected);
6120
- setBranchChoice(null);
6121
- await scanRepo(branchChoice.repo);
6122
- } catch (error) {
6123
- shared.fail(errorMessage(error));
6124
- } finally {
6125
- setBranchBusy(false);
6126
- }
6127
- }, [
6128
- api,
6129
- branchChoice,
6130
- scanRepo,
6131
- shared
6132
- ]);
6403
+ }
6404
+ }, [api, shared]);
6405
+ /** 手动输入框的添加动作。 */
6406
+ const addMarketSource = (0, react.useCallback)(async () => {
6407
+ await addSource(newSourceName);
6408
+ }, [addSource, newSourceName]);
6409
+ /** 删除一个市场源(不影响已装技能)。 */
6410
+ const removeMarketSource = (0, react.useCallback)(async (repo) => {
6411
+ shared.setTagBusy(true);
6412
+ await runFlow(shared, async () => {
6413
+ const result = await api.removeMarketSource(repo);
6414
+ setMarketState({
6415
+ status: "ready",
6416
+ repos: result.repos
6417
+ });
6418
+ }, () => shared.setTagBusy(false));
6419
+ }, [api, shared]);
6133
6420
  const openVersionDialog = (0, react.useCallback)(async (repo) => {
6134
6421
  const current = marketState.repos.find((item) => item.repo === repo)?.ref;
6135
6422
  setVersionDialog({
@@ -6167,17 +6454,12 @@ window.__ModuleLoader__.load({
6167
6454
  const ref = versionDialog.custom.trim() !== "" ? versionDialog.custom.trim() : versionDialog.selected;
6168
6455
  if (ref === "") return;
6169
6456
  setVersionBusy(true);
6170
- shared.clearFail();
6171
- try {
6457
+ await runFlow(shared, async () => {
6172
6458
  await api.setMarketSourceRef(versionDialog.repo, ref);
6173
6459
  setVersionDialog(null);
6174
6460
  await loadMarket();
6175
6461
  await scanRepo(versionDialog.repo);
6176
- } catch (error) {
6177
- shared.fail(errorMessage(error));
6178
- } finally {
6179
- setVersionBusy(false);
6180
- }
6462
+ }, () => setVersionBusy(false));
6181
6463
  }, [
6182
6464
  api,
6183
6465
  versionDialog,
@@ -6185,141 +6467,6 @@ window.__ModuleLoader__.load({
6185
6467
  scanRepo,
6186
6468
  shared
6187
6469
  ]);
6188
- /** Toggle one repo preview row. */
6189
- const toggleRepoSelected = (0, react.useCallback)((path, checked) => {
6190
- setRepoSelected((previous) => {
6191
- const next = new Set(previous);
6192
- if (checked) next.add(path);
6193
- else next.delete(path);
6194
- return next;
6195
- });
6196
- }, []);
6197
- /** Import every checked, non-existing repo skill (B方案:job+轮询+进度). */
6198
- const importRepo = (0, react.useCallback)(async () => {
6199
- if (repoDiscoverState.status !== "ready") return;
6200
- setRepoImporting(true);
6201
- setRepoResult(null);
6202
- setImportJobId(null);
6203
- shared.clearFail();
6204
- let finalProgress = null;
6205
- try {
6206
- const created = await api.repoImport(repoDiscoverState.data.repo, [...repoSelected], repoDiscoverState.data.ref ?? void 0);
6207
- setImportJobId(created.jobId);
6208
- setRepoResult({
6209
- ok: true,
6210
- jobId: created.jobId,
6211
- status: "running",
6212
- total: created.total,
6213
- done: 0,
6214
- totalBytes: created.totalBytes,
6215
- downloadedBytes: 0,
6216
- imported: [],
6217
- skipped: [],
6218
- failed: []
6219
- });
6220
- pollAbortRef.current?.abort();
6221
- pollAbortRef.current = new AbortController();
6222
- const signal = pollAbortRef.current.signal;
6223
- let attempt = 0;
6224
- for (;;) {
6225
- if (signal.aborted) break;
6226
- const delay = Math.min(2e3, 800 + attempt * 200);
6227
- await new Promise((r) => setTimeout(r, delay));
6228
- if (signal.aborted) break;
6229
- try {
6230
- const progress = await api.repoImportProgress(created.jobId);
6231
- setRepoResult(progress);
6232
- finalProgress = progress;
6233
- if (progress.status !== "running") break;
6234
- attempt = 0;
6235
- } catch (pollError) {
6236
- if (errorMessage(pollError).includes("not found")) break;
6237
- attempt += 1;
6238
- if (attempt > 8) break;
6239
- }
6240
- }
6241
- await Promise.all([
6242
- reloadCatalog(),
6243
- loadMarket(),
6244
- reloadGroups(),
6245
- reloadSources()
6246
- ]);
6247
- } catch (error) {
6248
- shared.fail(errorMessage(error));
6249
- } finally {
6250
- if (finalProgress !== null && (finalProgress.imported.length > 0 || finalProgress.skipped.length > 0)) {
6251
- const doneNames = /* @__PURE__ */ new Set([...finalProgress.imported.map((r) => r.name), ...finalProgress.skipped.map((r) => r.name)]);
6252
- const donePaths = new Set(repoDiscoverState.data.entries.filter((e) => doneNames.has(e.name)).map((e) => e.path));
6253
- setRepoSelected((prev) => {
6254
- const next = new Set(prev);
6255
- for (const p of donePaths) next.delete(p);
6256
- return next;
6257
- });
6258
- setRepoDiscoverState((prev) => {
6259
- if (prev.status !== "ready") return prev;
6260
- return {
6261
- ...prev,
6262
- data: {
6263
- ...prev.data,
6264
- entries: prev.data.entries.map((e) => doneNames.has(e.name) ? {
6265
- ...e,
6266
- existing: true
6267
- } : e)
6268
- }
6269
- };
6270
- });
6271
- }
6272
- setRepoImporting(false);
6273
- }
6274
- }, [
6275
- api,
6276
- repoDiscoverState,
6277
- repoSelected,
6278
- reloadCatalog,
6279
- loadMarket,
6280
- reloadGroups,
6281
- reloadSources,
6282
- shared
6283
- ]);
6284
- (0, react.useEffect)(() => {
6285
- return () => {
6286
- pollAbortRef.current?.abort();
6287
- };
6288
- }, []);
6289
- /** 取消正在进行的导入(选项2:唯有取消才停) */
6290
- const cancelImport = (0, react.useCallback)(async () => {
6291
- if (importJobId === null) return;
6292
- pollAbortRef.current?.abort();
6293
- try {
6294
- const res = await api.repoImportCancel(importJobId);
6295
- try {
6296
- const progress = await api.repoImportProgress(importJobId);
6297
- setRepoResult(progress);
6298
- } catch {
6299
- setRepoResult((prev) => prev !== null ? {
6300
- ...prev,
6301
- status: res.status
6302
- } : prev);
6303
- }
6304
- } catch (error) {
6305
- shared.fail(errorMessage(error));
6306
- } finally {
6307
- setRepoImporting(false);
6308
- }
6309
- }, [
6310
- api,
6311
- importJobId,
6312
- shared
6313
- ]);
6314
- /** 清空扫描结果(关闭归属卡片) */
6315
- const clearScan = (0, react.useCallback)(() => {
6316
- setRepoDiscoverState({ status: "idle" });
6317
- setScanningRepo(null);
6318
- setRepoResult(null);
6319
- setRepoSelected(/* @__PURE__ */ new Set());
6320
- setImportJobId(null);
6321
- pollAbortRef.current?.abort();
6322
- }, []);
6323
6470
  /** 检查所有市场源的上游更新(服务端节流)。 */
6324
6471
  const checkMarket = (0, react.useCallback)(async () => {
6325
6472
  try {
@@ -6388,8 +6535,7 @@ window.__ModuleLoader__.load({
6388
6535
  }, [api]),
6389
6536
  syncMarketSource: (0, react.useCallback)(async (repo) => {
6390
6537
  setSyncingMarket(repo);
6391
- shared.clearFail();
6392
- try {
6538
+ await runFlow(shared, async () => {
6393
6539
  const result = await api.marketSync(repo);
6394
6540
  setMarketSyncDialog({
6395
6541
  repo: result.repo,
@@ -6398,11 +6544,7 @@ window.__ModuleLoader__.load({
6398
6544
  selected: new Set(result.skills)
6399
6545
  });
6400
6546
  await loadMarket();
6401
- } catch (error) {
6402
- shared.fail(errorMessage(error));
6403
- } finally {
6404
- setSyncingMarket(null);
6405
- }
6547
+ }, () => setSyncingMarket(null));
6406
6548
  }, [
6407
6549
  api,
6408
6550
  loadMarket,
@@ -6412,8 +6554,7 @@ window.__ModuleLoader__.load({
6412
6554
  if (marketSyncDialog === null) return;
6413
6555
  const selected = [...marketSyncDialog.selected];
6414
6556
  setSyncBusy(true);
6415
- shared.clearFail();
6416
- try {
6557
+ await runFlow(shared, async () => {
6417
6558
  if (selected.length > 0) {
6418
6559
  const result = await api.syncSource(marketSyncDialog.repo, selected);
6419
6560
  if (result.failed.length > 0) shared.fail(result.failed.map((item) => item.name + ": " + item.error).join("\n"));
@@ -6425,11 +6566,7 @@ window.__ModuleLoader__.load({
6425
6566
  reloadSources()
6426
6567
  ]);
6427
6568
  checkMarket();
6428
- } catch (error) {
6429
- shared.fail(errorMessage(error));
6430
- } finally {
6431
- setSyncBusy(false);
6432
- }
6569
+ }, () => setSyncBusy(false));
6433
6570
  }, [
6434
6571
  api,
6435
6572
  marketSyncDialog,
@@ -6531,6 +6668,10 @@ window.__ModuleLoader__.load({
6531
6668
  return next;
6532
6669
  });
6533
6670
  }, []);
6671
+ /** 整组折叠/展开:传 null 全部展开,传键列表全部折叠(视图侧自己算键)。 */
6672
+ const setAllGroupsCollapsed = (0, react.useCallback)((keys) => {
6673
+ setCollapsedGroups(keys === null ? /* @__PURE__ */ new Set() : new Set(keys));
6674
+ }, []);
6534
6675
  const collapsePersonal = (0, react.useCallback)(() => {
6535
6676
  setCollapsedGroups((previous) => new Set(previous).add("uncategorized-source"));
6536
6677
  }, []);
@@ -6559,13 +6700,13 @@ window.__ModuleLoader__.load({
6559
6700
  meta.loadConfig();
6560
6701
  const fast = window.setInterval(() => {
6561
6702
  catalogFlow.load();
6562
- }, 5e3);
6703
+ }, POLL_MS);
6563
6704
  const slow = window.setInterval(() => {
6564
6705
  meta.loadUses();
6565
6706
  groupFlow.loadGroups();
6566
6707
  sourceFlow.loadSources();
6567
6708
  meta.loadConfig();
6568
- }, 6e4);
6709
+ }, SLOW_POLL_MS);
6569
6710
  return () => {
6570
6711
  window.clearInterval(fast);
6571
6712
  window.clearInterval(slow);
@@ -6595,7 +6736,6 @@ window.__ModuleLoader__.load({
6595
6736
  repoSelected: marketFlow.repoSelected,
6596
6737
  repoImporting: marketFlow.repoImporting,
6597
6738
  repoResult: marketFlow.repoResult,
6598
- importJobId: marketFlow.importJobId,
6599
6739
  search: catalogFlow.search,
6600
6740
  workspace: catalogFlow.workspace,
6601
6741
  detail: catalogFlow.detail,
@@ -6690,12 +6830,12 @@ window.__ModuleLoader__.load({
6690
6830
  setVersionDialog: marketFlow.setVersionDialog,
6691
6831
  toggleGroupCollapse,
6692
6832
  toggleSubdivide,
6833
+ setAllGroupsCollapsed,
6693
6834
  checkUpdate: meta.checkUpdate,
6694
6835
  loadMarket: marketFlow.loadMarket,
6695
6836
  openDetail: catalogFlow.openDetail,
6696
6837
  toggle: catalogFlow.toggle,
6697
6838
  enableDisabled: catalogFlow.enableDisabled,
6698
- batchToggleNames: catalogFlow.batchToggleNames,
6699
6839
  toggleGroup: groupFlow.toggleGroup,
6700
6840
  resolveConflict: groupFlow.resolveConflict,
6701
6841
  runConfirmed: sourceFlow.runConfirmed,
@@ -6717,7 +6857,6 @@ window.__ModuleLoader__.load({
6717
6857
  deleteTag: groupFlow.deleteTag,
6718
6858
  saveTag: groupFlow.saveTag,
6719
6859
  reorderTags: groupFlow.reorderTags,
6720
- reorderCollections: groupFlow.reorderCollections,
6721
6860
  reorderSourceGroups: groupFlow.reorderSourceGroups,
6722
6861
  addSource: marketFlow.addSource,
6723
6862
  addMarketSource: marketFlow.addMarketSource,
@@ -6746,16 +6885,19 @@ window.__ModuleLoader__.load({
6746
6885
  * detail inspection, and the new-skill scaffold form.
6747
6886
  *
6748
6887
  * Thin shell: state and flows live in useSkillHub, the tab contents live in
6749
- * SourcesView / ScenesView / MarketView, and the dialog family lives in
6750
- * dialogs.tsx. This component owns only the shared chrome (header, banners,
6751
- * filter bar, shared sections) and the view routing — and holds no hooks,
6752
- * so the detail / tag-editor early returns are safe.
6888
+ * SourcesView / ScenesView / MarketView, the dialog family lives in
6889
+ * dialogs.tsx, and their wiring lives in PanelDialogs.tsx. This component
6890
+ * owns only the shared chrome (header, banners, filter bar, shared sections)
6891
+ * and the view routing — and holds no hooks, so the detail / tag-editor
6892
+ * early returns are safe.
6753
6893
  */
6754
6894
  function SkillHubPanel(props) {
6755
6895
  const hub = useSkillHub(props.api);
6756
6896
  /** 工作区输入草稿:回车才应用,避免每次按键都触发目录重拉。 */
6757
6897
  const [workspaceDraft, setWorkspaceDraft] = (0, react.useState)("");
6758
- 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;
6898
+ /** 「筛选」面板开合(来源 + 调用方式收进这里;搜索/排序/视图切换始终可见)。 */
6899
+ const [filtersOpen, setFiltersOpen] = (0, react.useState)(false);
6900
+ const { catalog, loading, loadError, successBanner, updateState, detail, detailLoading, showForm, formName, formDesc, formRoot, formBusy, formMessage, hubConfig, tab, skillView, sourceFilter, sortKey, search, workspace, setWorkspace, sourcesState, tagBusy, batchBusy, 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;
6759
6901
  const { shortenedCount, fixingPaths, clearListFilters } = hub;
6760
6902
  if (detail !== null) {
6761
6903
  const disabledRecord = catalog?.disabled.find((record) => record.name === detail.name);
@@ -6768,7 +6910,6 @@ window.__ModuleLoader__.load({
6768
6910
  sourceCheck,
6769
6911
  checkingSource,
6770
6912
  syncingSource,
6771
- loading: detailLoading,
6772
6913
  disabled: disabledRecord !== void 0,
6773
6914
  onEnable: disabledRecord !== void 0 ? () => {
6774
6915
  enableDisabled(disabledRecord).then(() => {
@@ -6815,6 +6956,8 @@ window.__ModuleLoader__.load({
6815
6956
  saveTag(editingTag.id, editName, [...membersDraft]);
6816
6957
  }
6817
6958
  });
6959
+ /** 生效中的筛选条件数(来源 + 调用方式),显示在「筛选」按钮上。 */
6960
+ const activeFilterCount = (sourceFilter !== "all" ? 1 : 0) + (hub.invocationFilter !== "all" ? 1 : 0);
6818
6961
  /** 检查结果的悬停提示:收敛到「检查更新」按钮上,不再占面板顶部横幅。 */
6819
6962
  const updateTitle = (() => {
6820
6963
  if (updateState.status === "checking") return tt("update.checking");
@@ -6832,6 +6975,7 @@ window.__ModuleLoader__.load({
6832
6975
  })();
6833
6976
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6834
6977
  className: panel_module_css_default.panel,
6978
+ "aria-busy": batchBusy || tagBusy,
6835
6979
  children: [
6836
6980
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6837
6981
  className: panel_module_css_default.header,
@@ -6875,22 +7019,35 @@ window.__ModuleLoader__.load({
6875
7019
  }) : null,
6876
7020
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6877
7021
  className: panel_module_css_default.actions,
6878
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6879
- type: "button",
6880
- className: panel_module_css_default.button,
6881
- disabled: updateState.status === "checking",
6882
- title: updateTitle,
6883
- onClick: () => {
6884
- checkUpdate();
6885
- },
6886
- children: updateState.status === "checking" ? tt("update.checking") : tt("update.check")
6887
- }), updateState.status === "ready" && updateState.data.updateAvailable && updateState.data.url !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
6888
- className: panel_module_css_default.updateLink,
6889
- href: updateState.data.url,
6890
- target: "_blank",
6891
- rel: "noreferrer",
6892
- children: tt("update.newVersion", { version: updateState.data.latestVersion ?? "" })
6893
- }) : null]
7022
+ children: [
7023
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7024
+ type: "button",
7025
+ className: panel_module_css_default.button + (editMode ? " " + panel_module_css_default.primary : ""),
7026
+ "aria-pressed": editMode,
7027
+ title: tt("edit.hint"),
7028
+ onClick: () => {
7029
+ setEditMode((value) => !value);
7030
+ },
7031
+ children: tt(editMode ? "edit.done" : "edit.start")
7032
+ }),
7033
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7034
+ type: "button",
7035
+ className: panel_module_css_default.button,
7036
+ disabled: updateState.status === "checking",
7037
+ title: updateTitle,
7038
+ onClick: () => {
7039
+ checkUpdate();
7040
+ },
7041
+ children: updateState.status === "checking" ? tt("update.checking") : tt("update.check")
7042
+ }),
7043
+ updateState.status === "ready" && updateState.data.updateAvailable && updateState.data.url !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
7044
+ className: panel_module_css_default.updateLink,
7045
+ href: updateState.data.url,
7046
+ target: "_blank",
7047
+ rel: "noreferrer",
7048
+ children: tt("update.newVersion", { version: updateState.data.latestVersion ?? "" })
7049
+ }) : null
7050
+ ]
6894
7051
  })
6895
7052
  ]
6896
7053
  }),
@@ -7056,9 +7213,11 @@ window.__ModuleLoader__.load({
7056
7213
  }) : null,
7057
7214
  loadError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7058
7215
  className: panel_module_css_default.errorBanner,
7216
+ role: "alert",
7059
7217
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: loadError }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7060
7218
  type: "button",
7061
7219
  className: panel_module_css_default.button,
7220
+ "aria-label": tt("err.dismiss"),
7062
7221
  onClick: () => {
7063
7222
  setLoadError(null);
7064
7223
  },
@@ -7067,9 +7226,11 @@ window.__ModuleLoader__.load({
7067
7226
  }) : null,
7068
7227
  successBanner !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7069
7228
  className: panel_module_css_default.successBanner,
7229
+ role: "status",
7070
7230
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: successBanner }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7071
7231
  type: "button",
7072
7232
  className: panel_module_css_default.button,
7233
+ "aria-label": tt("err.dismiss"),
7073
7234
  onClick: () => {
7074
7235
  setSuccessBanner(null);
7075
7236
  },
@@ -7099,9 +7260,15 @@ window.__ModuleLoader__.load({
7099
7260
  })
7100
7261
  ]
7101
7262
  }) : null,
7102
- loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7103
- className: panel_module_css_default.empty,
7104
- children: tt("panel.loading")
7263
+ loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7264
+ className: panel_module_css_default.skeleton,
7265
+ role: "status",
7266
+ "aria-label": tt("panel.loading"),
7267
+ children: [
7268
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: panel_module_css_default.skeletonRow }),
7269
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: panel_module_css_default.skeletonRow }),
7270
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: panel_module_css_default.skeletonRow })
7271
+ ]
7105
7272
  }) : null,
7106
7273
  detailLoading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7107
7274
  className: panel_module_css_default.empty,
@@ -7111,7 +7278,7 @@ window.__ModuleLoader__.load({
7111
7278
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7112
7279
  className: panel_module_css_default.filterBar,
7113
7280
  children: [
7114
- tab === "sources" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7281
+ tab === "sources" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7115
7282
  className: panel_module_css_default.segmented,
7116
7283
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7117
7284
  type: "button",
@@ -7128,20 +7295,7 @@ window.__ModuleLoader__.load({
7128
7295
  },
7129
7296
  children: tt("view.grouped")
7130
7297
  })]
7131
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
7132
- className: panel_module_css_default.select,
7133
- value: sourceFilter,
7134
- onChange: (event) => {
7135
- setSourceFilter(event.target.value);
7136
- },
7137
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7138
- value: "all",
7139
- children: tt("filter.allSources")
7140
- }), sourceOptions.map((source) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7141
- value: source,
7142
- children: source === "private" ? tt("filter.private") : source
7143
- }, source))]
7144
- })] }) : null,
7298
+ }) : null,
7145
7299
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
7146
7300
  className: panel_module_css_default.select,
7147
7301
  value: sortKey,
@@ -7163,27 +7317,6 @@ window.__ModuleLoader__.load({
7163
7317
  })
7164
7318
  ]
7165
7319
  }),
7166
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
7167
- className: panel_module_css_default.select,
7168
- value: hub.invocationFilter,
7169
- onChange: (event) => {
7170
- hub.setInvocationFilter(event.target.value);
7171
- },
7172
- children: [
7173
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7174
- value: "all",
7175
- children: tt("filter.invocationAll")
7176
- }),
7177
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7178
- value: "model",
7179
- children: tt("filter.modelOnly")
7180
- }),
7181
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7182
- value: "user",
7183
- children: tt("filter.userOnly")
7184
- })
7185
- ]
7186
- }),
7187
7320
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
7188
7321
  className: panel_module_css_default.search,
7189
7322
  value: search,
@@ -7192,15 +7325,80 @@ window.__ModuleLoader__.load({
7192
7325
  },
7193
7326
  placeholder: tt("panel.search")
7194
7327
  }),
7195
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7328
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7196
7329
  type: "button",
7197
- className: panel_module_css_default.button + (editMode ? " " + panel_module_css_default.primary : ""),
7198
- style: { marginLeft: "auto" },
7199
- onClick: () => setEditMode((v) => !v),
7200
- children: editMode ? "完成" : "编辑"
7330
+ className: panel_module_css_default.button + (filtersOpen || activeFilterCount > 0 ? " " + panel_module_css_default.primary : ""),
7331
+ "aria-expanded": filtersOpen,
7332
+ "aria-controls": "skill-hub-filter-panel",
7333
+ onClick: () => {
7334
+ setFiltersOpen((value) => !value);
7335
+ },
7336
+ children: [tt("filter.title"), activeFilterCount > 0 ? " (" + activeFilterCount + ")" : ""]
7201
7337
  })
7202
7338
  ]
7203
7339
  }),
7340
+ filtersOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7341
+ className: panel_module_css_default.filterPanel,
7342
+ id: "skill-hub-filter-panel",
7343
+ children: [
7344
+ tab === "sources" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
7345
+ className: panel_module_css_default.filterField,
7346
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7347
+ className: panel_module_css_default.formLabel,
7348
+ children: tt("filter.source")
7349
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
7350
+ className: panel_module_css_default.select,
7351
+ value: sourceFilter,
7352
+ onChange: (event) => {
7353
+ setSourceFilter(event.target.value);
7354
+ },
7355
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7356
+ value: "all",
7357
+ children: tt("filter.allSources")
7358
+ }), sourceOptions.map((source) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7359
+ value: source,
7360
+ children: source === "private" ? tt("filter.private") : source
7361
+ }, source))]
7362
+ })]
7363
+ }) : null,
7364
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
7365
+ className: panel_module_css_default.filterField,
7366
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7367
+ className: panel_module_css_default.formLabel,
7368
+ children: tt("filter.invocation")
7369
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
7370
+ className: panel_module_css_default.select,
7371
+ value: hub.invocationFilter,
7372
+ onChange: (event) => {
7373
+ hub.setInvocationFilter(event.target.value);
7374
+ },
7375
+ children: [
7376
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7377
+ value: "all",
7378
+ children: tt("filter.invocationAll")
7379
+ }),
7380
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7381
+ value: "model",
7382
+ children: tt("filter.modelOnly")
7383
+ }),
7384
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7385
+ value: "user",
7386
+ children: tt("filter.userOnly")
7387
+ })
7388
+ ]
7389
+ })]
7390
+ }),
7391
+ activeFilterCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7392
+ type: "button",
7393
+ className: panel_module_css_default.opBtn,
7394
+ onClick: () => {
7395
+ setSourceFilter("all");
7396
+ hub.setInvocationFilter("all");
7397
+ },
7398
+ children: tt("filter.clear")
7399
+ }) : null
7400
+ ]
7401
+ }) : null,
7204
7402
  catalog !== null && (filtered.length !== catalog.skills.length || hub.invocationFilter !== "all" || sourceFilter !== "all") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7205
7403
  className: panel_module_css_default.hintLine,
7206
7404
  style: {
@@ -7305,130 +7503,37 @@ window.__ModuleLoader__.load({
7305
7503
  }, entry.path))]
7306
7504
  }) : null
7307
7505
  ] }) : null,
7308
- conflictDialog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConflictDialog, {
7309
- dialog: conflictDialog,
7506
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PanelDialogs, {
7507
+ conflictDialog,
7310
7508
  tags: groupsState?.tags ?? [],
7311
7509
  collections: groupsState?.collections ?? [],
7312
- onClose: () => {
7313
- setConflictDialog(null);
7314
- },
7315
- onKeepOn: () => {
7316
- resolveConflict(false);
7317
- },
7318
- onCloseAll: () => {
7319
- resolveConflict(true);
7320
- }
7321
- }) : null,
7322
- confirmDialog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmDialog, {
7323
- title: confirmDialog.kind === "sync" ? tt("source.syncConfirmTitle") : tt("source.deleteConfirmTitle"),
7324
- text: confirmDialog.kind === "sync" ? tt("source.syncConfirmText") : tt("source.deleteConfirmText"),
7325
- items: confirmDialog.skills,
7326
- confirmLabel: confirmDialog.kind === "sync" ? tt("source.sync") : tt("source.followDelete"),
7327
- danger: confirmDialog.kind === "delete",
7328
- onCancel: () => {
7329
- setConfirmDialog(null);
7330
- },
7331
- onConfirm: () => {
7332
- runConfirmed();
7333
- }
7334
- }) : null,
7335
- branchChoice !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BranchChoiceDialog, {
7336
- choice: branchChoice,
7337
- busy: branchBusy,
7338
- onSelect: (selected) => {
7339
- setBranchChoice({
7340
- ...branchChoice,
7341
- selected
7342
- });
7343
- },
7344
- onCancel: () => {
7345
- setBranchChoice(null);
7346
- },
7347
- onConfirm: () => {
7348
- confirmBranchChoice();
7349
- }
7350
- }) : null,
7351
- hub.versionDialog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VersionChoiceDialog, {
7352
- choice: hub.versionDialog,
7353
- busy: hub.versionBusy,
7354
- onSelect: (selected) => {
7355
- hub.setVersionDialog({
7356
- ...hub.versionDialog,
7357
- selected
7358
- });
7359
- },
7360
- onCustom: (custom) => {
7361
- hub.setVersionDialog({
7362
- ...hub.versionDialog,
7363
- custom
7364
- });
7365
- },
7366
- onCancel: () => {
7367
- hub.setVersionDialog(null);
7368
- },
7369
- onConfirm: () => {
7370
- hub.confirmVersionDialog();
7371
- }
7372
- }) : null,
7373
- marketSyncDialog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarketSyncDialog, {
7374
- dialog: marketSyncDialog,
7375
- busy: syncBusy,
7376
- onToggle: (name, checked) => {
7377
- const next = new Set(marketSyncDialog.selected);
7378
- if (checked) next.add(name);
7379
- else next.delete(name);
7380
- setMarketSyncDialog({
7381
- ...marketSyncDialog,
7382
- selected: next
7383
- });
7384
- },
7385
- onCancel: () => {
7386
- setMarketSyncDialog(null);
7387
- },
7388
- onConfirm: () => {
7389
- confirmMarketSync();
7390
- }
7391
- }) : null,
7392
- deleteSkillDialog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmDialog, {
7393
- title: tt("delete.confirmTitle"),
7394
- text: tt("delete.confirmText", { name: deleteSkillDialog }),
7395
- confirmLabel: tt("delete.confirm"),
7396
- danger: true,
7397
- onCancel: () => {
7398
- setDeleteSkillDialog(null);
7399
- },
7400
- onConfirm: () => {
7401
- runDeleteSkill();
7402
- }
7403
- }) : null,
7404
- deleteGroupDialog !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmDialog, {
7405
- title: tt("source.deleteGroupTitle"),
7406
- text: tt("source.deleteGroupText", {
7407
- name: deleteGroupDialog.name,
7408
- count: deleteGroupDialog.skillNames.length
7409
- }),
7410
- items: deleteGroupDialog.skillNames,
7411
- confirmLabel: tt("source.deleteGroup"),
7412
- danger: true,
7413
- onCancel: () => {
7414
- setDeleteGroupDialog(null);
7415
- },
7416
- onConfirm: () => {
7417
- runDeleteGroup();
7418
- }
7419
- }) : null,
7420
- confirmClearTrash ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmDialog, {
7421
- title: tt("source.clearTrashConfirmTitle"),
7422
- text: tt("source.clearTrashConfirmText"),
7423
- confirmLabel: tt("source.clearTrashConfirm"),
7424
- danger: true,
7425
- onCancel: () => {
7426
- setConfirmClearTrash(false);
7427
- },
7428
- onConfirm: () => {
7429
- clearTrash();
7430
- }
7431
- }) : null
7510
+ setConflictDialog,
7511
+ resolveConflict,
7512
+ confirmDialog,
7513
+ setConfirmDialog,
7514
+ runConfirmed,
7515
+ branchChoice,
7516
+ branchBusy,
7517
+ setBranchChoice,
7518
+ confirmBranchChoice,
7519
+ versionDialog: hub.versionDialog,
7520
+ versionBusy: hub.versionBusy,
7521
+ setVersionDialog: hub.setVersionDialog,
7522
+ confirmVersionDialog: hub.confirmVersionDialog,
7523
+ marketSyncDialog,
7524
+ syncBusy,
7525
+ setMarketSyncDialog,
7526
+ confirmMarketSync,
7527
+ deleteSkillDialog,
7528
+ setDeleteSkillDialog,
7529
+ runDeleteSkill,
7530
+ deleteGroupDialog,
7531
+ setDeleteGroupDialog,
7532
+ runDeleteGroup,
7533
+ confirmClearTrash,
7534
+ setConfirmClearTrash,
7535
+ clearTrash
7536
+ })
7432
7537
  ]
7433
7538
  });
7434
7539
  }