dsh-skill-browser 1.9.10 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +80 -81
  3. package/docs/images/panel-browse.png +0 -0
  4. package/docs/images/panel-ledger.png +0 -0
  5. package/lib/client.js +1381 -269
  6. package/lib/client.js.bak-6colimp-20261028-230915 +3748 -0
  7. package/lib/client.js.bak-auth12-20261029-020107 +3749 -0
  8. package/lib/client.js.bak-auth12b-20261029-020140 +3749 -0
  9. package/lib/client.js.bak-auth12c-20261029-020206 +3749 -0
  10. package/lib/client.js.bak-auth12d-20261029-020232 +3747 -0
  11. package/lib/client.js.bak-auto-20261028-171351 +3669 -0
  12. package/lib/client.js.bak-auto2-20261028-171422 +3676 -0
  13. package/lib/client.js.bak-auto3-20261028-171545 +3676 -0
  14. package/lib/client.js.bak-followball-20261028-224743 +3720 -0
  15. package/lib/client.js.bak-gearico-20261028-230937 +3748 -0
  16. package/lib/client.js.bak-glow-20261030 +3676 -0
  17. package/lib/client.js.bak-grid-20261028-220142 +3680 -0
  18. package/lib/client.js.bak-guide-20261029-003108 +3750 -0
  19. package/lib/client.js.bak-guide2-20261029-003213 +3751 -0
  20. package/lib/client.js.bak-guide3-20261029-003236 +3763 -0
  21. package/lib/client.js.bak-guide4-20261029-003249 +3768 -0
  22. package/lib/client.js.bak-guidefc-20261029-003535 +3775 -0
  23. package/lib/client.js.bak-guideico-20261029-013809 +3776 -0
  24. package/lib/client.js.bak-guideorder-20261029-004345 +3775 -0
  25. package/lib/client.js.bak-headbtn-20261028-232114 +3750 -0
  26. package/lib/client.js.bak-menu6col-20261028-225126 +3741 -0
  27. package/lib/client.js.bak-menu6col2-20261028-225348 +3741 -0
  28. package/lib/client.js.bak-menu6col2-20261028-225527 +3741 -0
  29. package/lib/client.js.bak-monobtn-20261028-230502 +3741 -0
  30. package/lib/client.js.bak-navsimple-20261029-015235 +3786 -0
  31. package/lib/client.js.bak-nobadge-20261029-015354 +3784 -0
  32. package/lib/client.js.bak-nodrag-20261028-170713 +3651 -0
  33. package/lib/client.js.bak-official-20261028-171240 +3653 -0
  34. package/lib/client.js.bak-official2-20261028-171304 +3668 -0
  35. package/lib/client.js.bak-renumber-20261028-215518 +3681 -0
  36. package/lib/client.js.bak-sbgap-20261029-013144 +3776 -0
  37. package/lib/client.js.bak-sbgap-20261029-013202 +3776 -0
  38. package/lib/client.js.bak-sbrowh-20261029-012307 +3776 -0
  39. package/lib/client.js.bak-sbrowh-20261029-012333 +3776 -0
  40. package/lib/client.js.bak-scope-sb-20261029-011711 +3776 -0
  41. package/lib/client.js.bak-skinclarity-20261029-011027 +3776 -0
  42. package/lib/client.js.bak-skincols-20261028-224614 +3720 -0
  43. package/lib/client.js.bak-star-20261028-231205 +3748 -0
  44. package/lib/client.js.bak-star-20261028-231233 +3748 -0
  45. package/lib/client.js.bak-sync-20261028-220538 +3680 -0
  46. package/lib/groups.js +171 -21
  47. package/lib/index.js +141 -2
  48. package/lib/mcp.js +853 -0
  49. package/locale/en.json +2 -2
  50. package/locale/zh.json +2 -2
  51. package/package.json +87 -87
  52. package/docs/images/skill-browser-panel.png +0 -0
package/lib/client.js CHANGED
@@ -83,10 +83,13 @@ window.__ModuleLoader__.load({
83
83
  EX_ORDER.push("27");
84
84
  EX["28"]={no:"28",name:"月白纸墨",en:"PAPER & INK",group:"克制精选",anim:"calm",vars:{bgA:"#f7f5ef",bgB:"#dcd8cd",edge:"rgba(60,55,45,.28)",spec:"rgba(255,255,255,.85)",hi:"rgba(255,255,255,.7)",lbc:"#2c2a24",trk:"rgba(60,55,45,.14)",stI:"#a09c92",stP:"#6e6a60",stA:"#33302a",stD:"#55524a",pbgA:"#faf9f5",pbgB:"#edeae1",pbd:"rgba(60,55,45,.16)",ptx:"#33302a",itm:"#6e6a60",itD:"#8b877d",cctx:"#6e6a60",chipBg:"rgba(60,55,45,.05)",hintC:"#8b877d",hair1:"#33302a",hair2:"#a09c92"},band:"%BAND%{position:absolute;inset:0;border-radius:inherit;pointer-events:none;background:radial-gradient(circle at 50% 122%,var(--st) 0%,transparent 54%);opacity:.13}"};
85
85
  EX_ORDER.push("28");
86
+ EX["129"]={no:"129",name:"官方深色",en:"OFFICIAL DARK",group:"官方主题",anim:"calm",vars:{bgA:"#2c2c2e",bgB:"#151517",edge:"rgba(255,255,255,.13)",spec:"rgba(255,255,255,.14)",hi:"rgba(255,255,255,.10)",lbc:"#f9fafb",trk:"rgba(255,255,255,.10)",stI:"#979da6",stP:"#4176e6",stA:"#5686fe",stD:"#22c55e",pbgA:"#1b1b1c",pbgB:"#0f1115",pbd:"rgba(255,255,255,.09)",ptx:"#e1e5ee",itm:"#adb2b8",itD:"#81858c",cctx:"#adb2b8",chipBg:"rgba(255,255,255,.05)",hintC:"#81858c",hair1:"#5686fe",hair2:"#4176e6"},band:"%BAND%{position:absolute;inset:0;border-radius:inherit;pointer-events:none;background:radial-gradient(circle at 50% 122%,var(--st) 0%,transparent 54%);opacity:0.10}"};
87
+ EX["130"]={no:"130",name:"官方浅色",en:"OFFICIAL LIGHT",group:"官方主题",anim:"calm",vars:{bgA:"#ffffff",bgB:"#e9ecf2",edge:"rgba(20,24,34,.14)",spec:"rgba(255,255,255,.92)",hi:"rgba(255,255,255,.80)",lbc:"#151517",trk:"rgba(20,24,34,.12)",stI:"#81858c",stP:"#4176e6",stA:"#4176e6",stD:"#22c55e",pbgA:"#ffffff",pbgB:"#f1f3f5",pbd:"rgba(20,24,34,.10)",ptx:"#151517",itm:"#43454a",itD:"#61666b",cctx:"#43454a",chipBg:"rgba(20,24,34,.05)",hintC:"#81858c",hair1:"#4176e6",hair2:"#7aaaff"},band:"%BAND%{position:absolute;inset:0;border-radius:inherit;pointer-events:none;background:radial-gradient(circle at 50% 122%,var(--st) 0%,transparent 54%);opacity:0.08}"};
86
88
  var KIND_LABEL={deep:"浓郁深底",muted:"尘埃灰调",soft:"柔光深底",neutral:"中性点缀",light:"浅色纸面"};
87
89
  var TINT="%BAND%{position:absolute;inset:0;border-radius:inherit;pointer-events:none;background:radial-gradient(circle at 50% 122%,var(--st) 0%,transparent 54%);opacity:.13}";
88
90
  var ALL=[],BY_ID={};
89
91
  (function build(){
92
+ ["129","130"].forEach(function(id){var s=EX[id];if(!s)return;s.id=id;s.kind="special";ALL.push(s);BY_ID[id]=s;});
90
93
  EX_ORDER.forEach(function(id){var s=EX[id];s.id=id;s.kind="special";ALL.push(s);BY_ID[id]=s;});
91
94
  var order=["deep","muted","soft","neutral","light"],no=28;
92
95
  order.forEach(function(kind){
@@ -97,28 +100,44 @@ window.__ModuleLoader__.load({
97
100
  ALL.push(s);BY_ID[id]=s;
98
101
  });
99
102
  });
103
+ // 统一显示序号(2026-10-29 用户要求):官方深色=1、官方浅色=2、其余按顺序 3..N
104
+ // 注意:只改显示用 no 字段,内部 id 保持不变(localStorage 兼容)
105
+ ALL.forEach(function(s,idx){ s.no = String(idx+1); });
100
106
  })();
101
- var KEY="dsh-ball-skin",DEFAULT_ID="01";
102
- function getId(){try{var v=localStorage.getItem(KEY);if(v&&BY_ID[v])return v;}catch(e){}return DEFAULT_ID;}
107
+ var KEY="dsh-ball-skin",AUTO_ID="auto",OFFICIAL_DARK="129",OFFICIAL_LIGHT="130",DEFAULT_ID="auto";
108
+ function isDark(){try{return !!(window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches);}catch(e){return true;}}
109
+ function pref(){try{var v=localStorage.getItem(KEY);if(v==="legacy")return "legacy";if(v&&(v===AUTO_ID||BY_ID[v]))return v;}catch(e){}return DEFAULT_ID;}
110
+ function resolve(id){
111
+ if(!id||id===AUTO_ID)return isDark()?OFFICIAL_DARK:OFFICIAL_LIGHT;
112
+ if(BY_ID[id])return id;
113
+ return isDark()?OFFICIAL_DARK:OFFICIAL_LIGHT;
114
+ }
115
+ function getId(){return resolve(pref());}
103
116
  function setSkin(id){
104
- if(!BY_ID[id])return false;
117
+ if(id!==AUTO_ID&&id!=="legacy"&&!BY_ID[id])return false;
105
118
  try{localStorage.setItem(KEY,id);}catch(e){}
106
119
  var hooks=window.__dshBallSkinHooks||[];
107
120
  for(var i=0;i<hooks.length;i++){try{hooks[i](id,BY_ID[id]);}catch(e){}}
108
121
  try{window.dispatchEvent(new CustomEvent("dsh-ball-skin-change",{detail:{id:id}}));}catch(e){}
109
122
  return true;
110
123
  }
111
- function varsCss(sel,id){var s=BY_ID[id]||BY_ID[DEFAULT_ID];var out=sel+"{";
124
+ function varsCss(sel,id){var s=BY_ID[resolve(id)]||ALL[0];var out=sel+"{";
112
125
  for(var k in s.vars){out+="--"+k+":"+s.vars[k]+";";}
113
126
  return out+"}";}
114
127
  function varsAll(sel){return varsCss(sel,getId());}
115
- function fx(id,sel){var s=BY_ID[id]||BY_ID[DEFAULT_ID];var out=[];
128
+ function fx(id,sel){var s=BY_ID[resolve(id)]||ALL[0];var out=[];
116
129
  if(s.rim)out.push(s.rim.split("%RIM%").join(sel.RIM));
117
130
  if(s.band)out.push(s.band.split("%BAND%").join(sel.BAND));
118
131
  if(s.extra)out.push(s.extra.split("%B%").join(sel.B));
119
132
  return out.join("\n");}
120
133
  function groups(){var g=[],seen={};ALL.forEach(function(s){if(!seen[s.group]){seen[s.group]=true;g.push(s.group);}});return g;}
121
- window.__DSH_BALL_SKINS={ALL:ALL,BY_ID:BY_ID,getId:getId,setSkin:setSkin,varsCss:varsCss,varsAll:varsAll,fx:fx,groups:groups,KIND_LABEL:KIND_LABEL,KEY:KEY,DEFAULT_ID:DEFAULT_ID};
134
+ window.__DSH_BALL_SKINS={ALL:ALL,BY_ID:BY_ID,getId:getId,getPref:pref,pref:pref,resolve:resolve,isDark:isDark,setSkin:setSkin,varsCss:varsCss,varsAll:varsAll,fx:fx,groups:groups,KIND_LABEL:KIND_LABEL,KEY:KEY,AUTO_ID:AUTO_ID,OFFICIAL_DARK:OFFICIAL_DARK,OFFICIAL_LIGHT:OFFICIAL_LIGHT,DEFAULT_ID:DEFAULT_ID};
135
+ // 跟随客户端深/浅色:DSH 用 prefers-color-scheme(无 data-theme),变化时通知三球重绘
136
+ try{
137
+ var _mq=window.matchMedia?window.matchMedia("(prefers-color-scheme: dark)"):null;
138
+ function _onScheme(){try{window.dispatchEvent(new CustomEvent("dsh-ball-skin-change",{detail:{id:getId(),auto:true}}));}catch(e){}}
139
+ if(_mq){if(_mq.addEventListener)_mq.addEventListener("change",_onScheme);else if(_mq.addListener)_mq.addListener(_onScheme);}
140
+ }catch(e){}
122
141
  })();
123
142
 
124
143
  // v1.5.5(2026-09-06):版本号对齐修复——host 端 index.js 的 VERSION
@@ -156,10 +175,12 @@ window.__ModuleLoader__.load({
156
175
  mounted: false,
157
176
  open: false,
158
177
  panelBuilt: false,
159
- view: "skills", // "skills" | "ledger"
178
+ view: "skills", // "skills" | "ledger" | "guide" | "mcp"
160
179
  data: null,
161
180
  ledger: null,
162
181
  ledgerDetail: null,
182
+ // ── v2.0.0 MCP 管理 ──
183
+ mcp: null, // { data, stats, loading, statsLoading, error, detailId, busy, exportText, showExport }
163
184
  loading: false,
164
185
  error: "",
165
186
  cat: "all",
@@ -167,6 +188,7 @@ window.__ModuleLoader__.load({
167
188
  q: "",
168
189
  detail: null,
169
190
  sortMode: "name-asc", // v1.7.1 四态:"name-asc" | "name-desc" | "mtime-desc" | "mtime-asc"
191
+ cardCols: (function () { try { var v = parseInt(localStorage.getItem("dsb-card-cols"), 10); return (v >= 2 && v <= 6) ? v : 0; } catch (e) { return 0; } })(), // v2.0.6 技能卡片每行列数(0=自适应)
170
192
  toggling: false, // v1.7.0: toggle 请求进行中(防重复点击)
171
193
  toastTimer: null, // v1.7.1: toast 自动消失定时器句柄
172
194
  batchMenuScope: null, // v1.7.2: 批量确认菜单当前作用域("global" 或分类 id)
@@ -177,12 +199,22 @@ window.__ModuleLoader__.load({
177
199
  group: "all", // "all" | "ungrouped" | <groupId>
178
200
  groupsLoaded: false,
179
201
  groupBusy: "", // "" | "ai-classify" | "ai-cover:<gid>"
202
+ aiStartTs: 0, // v2.0.13 AI/关键词分组计时起点
203
+ aiElapsedMs: 0, // 本次分组耗时(毫秒)
204
+ aiCached: false, // 是否命中结果缓存
205
+ aiTimer: null, // 计时器句柄
206
+ aiLastMs: (function () { try { var v = parseInt(localStorage.getItem("dsb-ai-last-ms"), 10); return v > 0 ? v : 0; } catch (e) { return 0; } })(),
207
+ aiLastCached: (function () { try { return localStorage.getItem("dsb-ai-last-cached") === "1"; } catch (e) { return false; } })(),
208
+ aiLastMode: (function () { try { return localStorage.getItem("dsb-ai-last-mode") || ""; } catch (e) { return ""; } })(),
209
+ aiLastParallel: (function () { try { return localStorage.getItem("dsb-ai-last-par") === "1"; } catch (e) { return false; } })(),
210
+ aiLastBatches: (function () { try { var v = parseInt(localStorage.getItem("dsb-ai-last-bat"), 10); return v > 0 ? v : 0; } catch (e) { return 0; } })(),
180
211
  moveMenuDir: null, // 当前打开"移动到分组"菜单的技能目录
181
212
  // ── v1.9.3 两级分组(大类 → 子类)──
182
213
  gplan: null,
183
214
  gplanMode: "", // AI 产出的待确认方案 {domains:[{name,emoji,subs:[{name,emoji,skills:[]}]}]}
184
215
  gplanBusy: false, // 方案确认弹层是否已打开
185
216
  gsub: "", // 当前选中大类下再选中的子类 id(""=看整个大类)
217
+ gFold: false, // v2.0.12 分组树子类行:true=全部收起 / false=全部展开
186
218
  gmode: "tree", // v1.9.8 分组栏显示模式:"tree" 树状(大类一行+常驻子类)| "flat" 平铺(原样)
187
219
  modelInfo: null, // v1.9.9 AI 模型探针结果 {ok,provider,model,source,error};null=还没探
188
220
  newParent: "", // "新建分组"时选定的上级大类 id(""=建大类)
@@ -451,9 +483,11 @@ window.__ModuleLoader__.load({
451
483
  if (S.group !== "all") {
452
484
  var gid = S.gassign[s.dir] || "";
453
485
  if (S.group === "ungrouped") { if (gid) continue; }
454
- else if (S.gsub) { if (gid !== S.gsub) continue; }
455
486
  else if (!inDomain(S.group, gid)) continue;
456
487
  }
488
+ // v2.0.8 修复:子类过滤独立生效。树状模式直接点子类时 S.group 仍是 all,
489
+ // 原来 gsub 判断被 S.group!=="all" 挡住 → 点了子类列表不变(平铺因先选大类才正常)。
490
+ if (S.gsub && (S.gassign[s.dir] || "") !== S.gsub) continue;
457
491
  if (q) {
458
492
  var hay = (s.dir + " " + s.name + " " + (s.zh || "") + " " + (s.desc || "")).toLowerCase();
459
493
  if (hay.indexOf(q) < 0) continue;
@@ -605,19 +639,47 @@ window.__ModuleLoader__.load({
605
639
  .catch(function (e) { if (cb) cb({ ok: false, error: String((e && e.message) || e) }); });
606
640
  }
607
641
 
642
+ // v2.0.15 Electron 不支持 window.prompt(返回 undefined)→ 新建/重命名/建子类全点不动。
643
+ // 改:面板内统一输入弹层。
644
+ function openTextPrompt(title, defVal, cb) {
645
+ var panel = $(PANEL_ID); if (!panel) return;
646
+ var old = panel.querySelector(".dsb-tprompt"); if (old && old.parentNode) old.parentNode.removeChild(old);
647
+ var wrap = document.createElement("div");
648
+ wrap.className = "dsb-tprompt";
649
+ wrap.innerHTML = '<div class="dsb-tprompt-box">' +
650
+ '<div class="dsb-tprompt-title">' + esc(title) + '</div>' +
651
+ '<input class="dsb-tprompt-inp" type="text" />' +
652
+ '<div class="dsb-tprompt-ops">' +
653
+ '<button class="dsb-btn" data-tp="cancel">取消</button>' +
654
+ '<button class="dsb-btn" data-tp="ok">确定</button>' +
655
+ '</div></div>';
656
+ panel.appendChild(wrap);
657
+ var inp = wrap.querySelector(".dsb-tprompt-inp");
658
+ inp.value = defVal || "";
659
+ setTimeout(function () { try { inp.focus(); inp.select(); } catch (e) { } }, 30);
660
+ function close() { if (wrap.parentNode) wrap.parentNode.removeChild(wrap); }
661
+ function ok() { var v = String(inp.value || "").trim(); close(); if (cb) cb(v); }
662
+ wrap.addEventListener("click", function (ev) {
663
+ var t = ev.target.closest("[data-tp]"); if (!t) return;
664
+ if (t.getAttribute("data-tp") === "ok") ok(); else close();
665
+ });
666
+ inp.addEventListener("keydown", function (ev) {
667
+ if (ev.key === "Enter") { ev.preventDefault(); ok(); }
668
+ else if (ev.key === "Escape") { ev.preventDefault(); close(); }
669
+ });
670
+ }
608
671
  function createGroupFlow() {
609
- var name = "";
610
- try { name = window.prompt(T("groupNamePrompt"), ""); } catch (e) { name = ""; }
611
- if (name == null) return;
612
- name = String(name).trim();
613
- if (!name) return;
614
- postGroups({ action: "create", name: name }, function (d) {
615
- if (d && d.ok) {
616
- showToast("ok", TF("groupCreated", { name: name }));
617
- loadGroups(function () { S.group = (d.group && d.group.id) || S.group; paint(); });
618
- } else {
619
- showToast("err", (d && d.error) || "创建失败");
620
- }
672
+ openTextPrompt(T("groupNamePrompt"), "", function (name0) {
673
+ var name = String(name0 || "").trim();
674
+ if (!name) return;
675
+ postGroups({ action: "create", name: name }, function (d) {
676
+ if (d && d.ok) {
677
+ showToast("ok", TF("groupCreated", { name: name }));
678
+ loadGroups(function () { S.group = (d.group && d.group.id) || S.group; paint(); });
679
+ } else {
680
+ showToast("err", (d && d.error) || "创建失败");
681
+ }
682
+ });
621
683
  });
622
684
  }
623
685
 
@@ -625,36 +687,34 @@ window.__ModuleLoader__.load({
625
687
  function createSubGroupFlow(parentId) {
626
688
  var par = groupById(parentId);
627
689
  if (!par) return;
628
- var name = "";
629
- try { name = window.prompt(TF("groupSubPrompt", { p: par.name }), ""); } catch (e) { return; }
630
- if (name == null) return;
631
- name = String(name).trim();
632
- if (!name) return;
633
- postGroups({ action: "create", name: name, parent: parentId }, function (d) {
634
- if (d && d.ok) {
635
- showToast("ok", TF("groupSubCreated", { name: name }));
636
- loadGroups(function () { S.group = parentId; S.gsub = (d.group && d.group.id) || ""; paint(); });
637
- } else {
638
- showToast("err", (d && d.error) || T("groupSubFail"));
639
- }
690
+ openTextPrompt(TF("groupSubPrompt", { p: par.name }), "", function (name0) {
691
+ var name = String(name0 || "").trim();
692
+ if (!name) return;
693
+ postGroups({ action: "create", name: name, parent: parentId }, function (d) {
694
+ if (d && d.ok) {
695
+ showToast("ok", TF("groupSubCreated", { name: name }));
696
+ loadGroups(function () { S.group = parentId; S.gsub = (d.group && d.group.id) || ""; paint(); });
697
+ } else {
698
+ showToast("err", (d && d.error) || T("groupSubFail"));
699
+ }
700
+ });
640
701
  });
641
702
  }
642
703
 
643
704
  function renameGroupFlow(id) {
644
705
  var g = groupById(id);
645
706
  if (!g) return;
646
- var name = "";
647
- try { name = window.prompt(T("groupNamePrompt"), g.name); } catch (e) { return; }
648
- if (name == null) return;
649
- name = String(name).trim();
650
- if (!name || name === g.name) return;
651
- postGroups({ action: "rename", id: id, name: name }, function (d) {
652
- if (d && d.ok) {
653
- showToast("ok", TF("groupRenamed", { name: name }));
654
- loadGroups(function () { paint(); });
655
- } else {
656
- showToast("err", (d && d.error) || "重命名失败");
657
- }
707
+ openTextPrompt(T("groupNamePrompt"), g.name, function (name0) {
708
+ var name = String(name0 || "").trim();
709
+ if (!name || name === g.name) return;
710
+ postGroups({ action: "rename", id: id, name: name }, function (d) {
711
+ if (d && d.ok) {
712
+ showToast("ok", TF("groupRenamed", { name: name }));
713
+ loadGroups(function () { paint(); });
714
+ } else {
715
+ showToast("err", (d && d.error) || "重命名失败");
716
+ }
717
+ });
658
718
  });
659
719
  }
660
720
 
@@ -675,17 +735,59 @@ window.__ModuleLoader__.load({
675
735
  });
676
736
  }
677
737
 
738
+ // v2.0.13 分组计时:进行中实时刷新 AI 按钮 + 面板内进度浮窗;结束后在方案弹层显示累计用时。
739
+ function aiProgEl(create) {
740
+ var panel = $(PANEL_ID); if (!panel) return null;
741
+ var el = panel.querySelector(".dsb-ai-prog");
742
+ if (!el && create) {
743
+ el = document.createElement("div");
744
+ el.className = "dsb-ai-prog";
745
+ el.innerHTML = '<div class="dsb-ai-prog-txt"></div><div class="dsb-ai-prog-bar"><i></i></div>';
746
+ panel.appendChild(el);
747
+ }
748
+ return el;
749
+ }
750
+ function stopAiTimer() {
751
+ if (S.aiTimer) { clearInterval(S.aiTimer); S.aiTimer = null; }
752
+ var el = aiProgEl(false);
753
+ if (el && el.parentNode) el.parentNode.removeChild(el);
754
+ }
755
+ function startAiTimer() {
756
+ stopAiTimer();
757
+ var EXPECT = 40; // 预计秒数(仅用于进度提示,不参与实际超时)
758
+ S.aiTimer = setInterval(function () {
759
+ try {
760
+ if (S.groupBusy !== "ai-classify") { stopAiTimer(); return; }
761
+ var sec = (Date.now() - S.aiStartTs) / 1000;
762
+ var btn = document.querySelector('#dsh-skill-browser-panel [data-gadd="ai"]');
763
+ if (btn) btn.innerHTML = "🤖 AI 分组中… " + sec.toFixed(1) + "s";
764
+ var el = aiProgEl(true);
765
+ if (el) {
766
+ var t = el.querySelector(".dsb-ai-prog-txt");
767
+ var bar = el.querySelector(".dsb-ai-prog-bar > i");
768
+ if (t) t.textContent = "🤖 AI 分组中 · 已用 " + sec.toFixed(1) + "s / 预计约 " + EXPECT + "s";
769
+ if (bar) bar.style.width = Math.min(97, Math.round(sec / EXPECT * 100)) + "%";
770
+ }
771
+ } catch (e) { }
772
+ }, 200);
773
+ }
774
+
678
775
  // v1.9.7: 关键词快速分组 —— 纯本地正则,零 token、毫秒级、结果确定。
679
776
  // 与 AI 分组共用同一个方案弹层与 applyPlan 落盘路径。
680
777
  function kwClassifyFlow() {
681
778
  if (S.groupBusy) return;
682
779
  S.groupBusy = "kw-classify";
780
+ S.aiStartTs = Date.now(); S.aiElapsedMs = 0; S.aiCached = false;
683
781
  paintGroups();
684
782
  postGroups({ action: "kw-classify" }, function (d) {
685
783
  S.groupBusy = "";
784
+ S.aiElapsedMs = Date.now() - S.aiStartTs;
686
785
  if (d && d.ok) {
687
786
  S.gplan = d.plan || null;
688
787
  S.gplanMode = "keyword";
788
+ S.aiLastMs = S.aiElapsedMs; S.aiLastCached = false; S.aiLastMode = "keyword";
789
+ S.aiLastParallel = false; S.aiLastBatches = 1;
790
+ try { localStorage.setItem("dsb-ai-last-ms", String(S.aiLastMs)); localStorage.setItem("dsb-ai-last-cached", "0"); localStorage.setItem("dsb-ai-last-mode", "keyword"); localStorage.setItem("dsb-ai-last-par", "0"); localStorage.setItem("dsb-ai-last-bat", "1"); } catch (eL) { }
689
791
  paintGroups();
690
792
  openPlanMenu();
691
793
  } else {
@@ -699,13 +801,21 @@ window.__ModuleLoader__.load({
699
801
  function aiClassifyFlow() {
700
802
  if (S.groupBusy) return;
701
803
  S.groupBusy = "ai-classify";
804
+ S.aiStartTs = Date.now(); S.aiElapsedMs = 0; S.aiCached = false;
702
805
  paintGroups();
703
806
  showToast("ok", T("groupAiRunning"));
807
+ startAiTimer();
704
808
  postGroups({ action: "ai-classify" }, function (d) {
809
+ stopAiTimer();
705
810
  S.groupBusy = "";
811
+ S.aiElapsedMs = Date.now() - S.aiStartTs;
812
+ S.aiCached = !!(d && d.cached);
706
813
  if (d && d.ok) {
707
814
  S.gplan = d.plan || null;
708
815
  S.gplanMode = "ai";
816
+ S.aiLastMs = S.aiElapsedMs; S.aiLastCached = S.aiCached; S.aiLastMode = "ai";
817
+ S.aiLastParallel = !!(d && d.parallel); S.aiLastBatches = (d && d.batches) || 1;
818
+ try { localStorage.setItem("dsb-ai-last-ms", String(S.aiLastMs)); localStorage.setItem("dsb-ai-last-cached", S.aiLastCached ? "1" : "0"); localStorage.setItem("dsb-ai-last-mode", "ai"); localStorage.setItem("dsb-ai-last-par", S.aiLastParallel ? "1" : "0"); localStorage.setItem("dsb-ai-last-bat", String(S.aiLastBatches)); } catch (eL) { }
709
819
  paintGroups();
710
820
  openPlanMenu();
711
821
  } else {
@@ -741,11 +851,21 @@ window.__ModuleLoader__.load({
741
851
  "</div>";
742
852
  }
743
853
  var planTitle = S.gplanMode === "keyword" ? T("groupPlanTitleKw") : T("groupPlanTitle");
854
+ var timeLine = "";
855
+ if (S.gplanMode === "keyword") {
856
+ timeLine = '<div class="dsb-plan-time">⚡ 关键词分组 · 用时 ' + (Math.max(0, S.aiElapsedMs) / 1000).toFixed(2) + " 秒(不耗 token)</div>";
857
+ } else if (S.aiCached) {
858
+ timeLine = '<div class="dsb-plan-time">⚡ 命中缓存 · 0.0 秒(未调用模型)</div>';
859
+ } else if (S.aiElapsedMs > 0) {
860
+ timeLine = '<div class="dsb-plan-time">⏱ AI 分组累计用时 ' + (S.aiElapsedMs / 1000).toFixed(1) + " 秒" + (S.aiLastBatches > 1 ? (" · 并行" + S.aiLastBatches + "批") : " · 串行") + "</div>";
861
+ }
744
862
  var html = '<div class="dsb-bmenu-title">' + esc(planTitle) + "</div>" +
745
863
  '<div class="dsb-plan-stat">' + esc(TF("groupPlanStat", { d: domN, s: subN, n: total })) + "</div>" +
864
+ timeLine +
746
865
  '<div class="dsb-plan-list">' + rows + "</div>" +
747
866
  '<button class="dsb-bmenu-item dsb-bmenu-danger" data-plan-act="replace"><span>' + esc(T("groupPlanReplace")) + "</span></button>" +
748
867
  '<button class="dsb-bmenu-item" data-plan-act="merge"><span>' + esc(T("groupPlanMerge")) + "</span></button>" +
868
+ '<button class="dsb-plan-recache" data-plan-act="recache">🔄 清除缓存并重新生成</button>' +
749
869
  '<button class="dsb-bmenu-cancel" data-plan-act="__cancel">' + esc(T("aggCancelText")) + "</button>";
750
870
  menu.className = "dsb-bmenu dsb-plan-menu";
751
871
  menu.innerHTML = html;
@@ -922,6 +1042,776 @@ window.__ModuleLoader__.load({
922
1042
  });
923
1043
  }
924
1044
 
1045
+ // ═══════════════════════ v2.0.0 MCP 管理 ═══════════════════════
1046
+ function mcpEnsure() {
1047
+ if (!S.mcp) {
1048
+ var savedMode = "tree";
1049
+ try { if (localStorage.getItem("dsb-mcp-mode") === "card") savedMode = "card"; } catch (e) { }
1050
+ S.mcp = {
1051
+ data: null, stats: null, loading: false, statsLoading: false, error: "",
1052
+ detailId: null, busy: "", showExport: false, exportText: "",
1053
+ mode: savedMode, // "tree" | "card"
1054
+ expanded: {}, // { mcpId: true } 树状展开集合
1055
+ residue: null, // 残留检测结果
1056
+ showResidue: false, // 是否显示残留确认区
1057
+ cols: (function () { try { var v = parseInt(localStorage.getItem("dsb-mcp-cols"), 10); return (v >= 2 && v <= 6) ? v : 0; } catch (e) { return 0; } })() // 卡片每行列数(0=自适应)
1058
+ };
1059
+ }
1060
+ return S.mcp;
1061
+ }
1062
+
1063
+ // ── v2.0.1 工具清单中文化:内置词典(本机已知 MCP 工具精确中文名)+ 关键词兜底 ──
1064
+ var MCP_TOOL_ZH = {
1065
+ // ansys-fluent(12)
1066
+ fluent_detect_tool: "检测 Fluent 与环境",
1067
+ fluent_run_journal_tool: "运行 Fluent Journal 作业",
1068
+ fluent_job_status_tool: "查询作业状态",
1069
+ fluent_job_log_tool: "读取作业日志",
1070
+ fluent_list_jobs_tool: "列出作业",
1071
+ fluent_launch_session_tool: "启动 Fluent 会话",
1072
+ fluent_list_sessions_tool: "列出活动会话",
1073
+ fluent_session_info_tool: "查看会话详情",
1074
+ fluent_execute_scheme_tool: "执行 Scheme 表达式",
1075
+ fluent_run_tui_tool: "执行 TUI 命令",
1076
+ fluent_run_python_tool: "执行 Python 脚本",
1077
+ fluent_close_session_tool: "关闭会话",
1078
+ // windows-mcp 常见
1079
+ Snapshot: "桌面快照(截图+元素树)",
1080
+ DesktopSnapshot: "桌面快照(截图+元素树)",
1081
+ Screenshot: "屏幕截图",
1082
+ Click: "鼠标点击",
1083
+ Move: "移动鼠标 / 拖拽",
1084
+ Type: "键入文本",
1085
+ Scroll: "滚动页面",
1086
+ Shortcut: "键盘快捷键",
1087
+ Clipboard: "剪贴板读取 / 写入",
1088
+ Process: "进程列表 / 结束进程",
1089
+ Registry: "注册表读取 / 写入",
1090
+ Scrape: "抓取网页内容",
1091
+ Wait: "等待指定时长",
1092
+ WaitFor: "等待条件满足",
1093
+ Notification: "发送系统通知",
1094
+ ControlStatus: "读取桌面控制状态",
1095
+ DisplayInventory: "读取显示器信息",
1096
+ MultiEdit: "批量填充输入框",
1097
+ MultiSelect: "多选 / 多击",
1098
+ PowerShell: "执行 PowerShell 命令",
1099
+ App: "应用启动 / 管理",
1100
+ FileSystem: "文件系统读写"
1101
+ };
1102
+ // 前缀动词兜底:未在词典里的工具,按名字前缀给出中文动作词
1103
+ var MCP_VERB_ZH = [
1104
+ [/^(get|read|query|fetch|search|find|inspect|show|describe|stat)/i, "查询"],
1105
+ [/^(list|enumerate)/i, "列出"],
1106
+ [/^(set|write|update|patch|edit|put|save|config)/i, "设置"],
1107
+ [/^(create|add|insert|new|make)/i, "创建"],
1108
+ [/^(delete|remove|drop|clear|reset|purge)/i, "删除"],
1109
+ [/^(run|execute|exec|eval|invoke|call|apply|solve)/i, "执行"],
1110
+ [/^(launch|start|open|spawn|boot)/i, "启动"],
1111
+ [/^(close|stop|kill|cancel|abort|terminate)/i, "关闭"],
1112
+ [/^(export|dump|download|backup)/i, "导出"],
1113
+ [/^(import|upload|load|restore)/i, "导入"],
1114
+ [/^(click|tap|press)/i, "点击"],
1115
+ [/^(move|drag|scroll)/i, "移动"],
1116
+ [/^(type|input|fill|send)/i, "输入"],
1117
+ [/^(wait|sleep|poll)/i, "等待"],
1118
+ [/^(check|validate|verify|test|probe)/i, "检查"],
1119
+ [/^(detect|discover|scan)/i, "检测"]
1120
+ ];
1121
+ function mcpToolZh(rawName, desc) {
1122
+ var name = String(rawName || "");
1123
+ if (MCP_TOOL_ZH[name]) return MCP_TOOL_ZH[name];
1124
+ // 去掉常见后缀再试(_tool / Tool)
1125
+ var base = name.replace(/(_tool|Tool|_cmd|Cmd)$/, "");
1126
+ if (MCP_TOOL_ZH[base]) return MCP_TOOL_ZH[base];
1127
+ for (var i = 0; i < MCP_VERB_ZH.length; i++) {
1128
+ if (MCP_VERB_ZH[i][0].test(base)) {
1129
+ var tail = base.replace(MCP_VERB_ZH[i][0], "").replace(/[_\-\s]+/g, " ").trim();
1130
+ return MCP_VERB_ZH[i][1] + (tail ? " " + tail : "");
1131
+ }
1132
+ }
1133
+ return "";
1134
+ }
1135
+
1136
+ function mcpPill(phase) {
1137
+ if (phase === "active") return { cls: "dsb-pill-active", text: "已连接" };
1138
+ if (phase === "failed") return { cls: "dsb-pill-failed", text: "连接失败" };
1139
+ if (phase === "disabled") return { cls: "dsb-pill-off", text: "已停用" };
1140
+ if (phase === "loading" || phase === "pending" || phase === "unloading") return { cls: "dsb-pill-warn", text: "加载中" };
1141
+ return { cls: "dsb-pill-off", text: "未激活" };
1142
+ }
1143
+
1144
+ function fmtMoment(ts) {
1145
+ if (!ts) return "";
1146
+ var d = new Date(ts), now = new Date();
1147
+ var p = function (n) { return n < 10 ? "0" + n : String(n); };
1148
+ var hm = p(d.getHours()) + ":" + p(d.getMinutes());
1149
+ var same = d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth() && d.getDate() === now.getDate();
1150
+ return same ? hm : (p(d.getMonth() + 1) + "-" + p(d.getDate()) + " " + hm);
1151
+ }
1152
+
1153
+ function loadMcp(force) {
1154
+ var M = mcpEnsure();
1155
+ if (M.loading) return;
1156
+ M.loading = true; M.error = "";
1157
+ paintMcp();
1158
+ var url = apiBase() + "/mcp/servers" + (force ? "?t=" + Date.now() : "");
1159
+ // 主数据与统计分开请求:一个失败绝不整块清空
1160
+ Promise.all([
1161
+ fetch(url).then(function (r) { return r.json(); }),
1162
+ fetch(apiBase() + "/mcp/stats").then(function (r) { return r.json(); }).catch(function () { return null; })
1163
+ ]).then(function (arr) {
1164
+ var d = arr[0], st = arr[1];
1165
+ M.loading = false;
1166
+ if (!d || !d.ok) throw new Error((d && d.error) || "bad response");
1167
+ M.data = d;
1168
+ if (st && st.ok) M.stats = st;
1169
+ if (!M.expandedInited) {
1170
+ M.expandedInited = true;
1171
+ M.expanded["__ROOT__"] = true;
1172
+ (d.servers || []).forEach(function (s) { M.expanded[s.id] = true; });
1173
+ }
1174
+ paintMcp();
1175
+ }).catch(function (e) {
1176
+ M.loading = false;
1177
+ M.error = String((e && e.message) || e);
1178
+ paintMcp();
1179
+ });
1180
+ }
1181
+
1182
+ function loadMcpStats() {
1183
+ var M = mcpEnsure();
1184
+ M.statsLoading = true; paintMcp();
1185
+ fetch(apiBase() + "/mcp/stats").then(function (r) { return r.json(); }).then(function (st) {
1186
+ M.statsLoading = false;
1187
+ if (st && st.ok) M.stats = st;
1188
+ paintMcp();
1189
+ }).catch(function () { M.statsLoading = false; paintMcp(); });
1190
+ }
1191
+
1192
+ function mcpDetailHtml(s, st) {
1193
+ var h = '<div class="dsb-mcp-detail">';
1194
+ if (s.tools && s.tools.length) {
1195
+ h += '<div style="margin-bottom:5px;font-weight:600">工具清单(' + s.tools.length + ')</div><div class="dsb-mcp-tools">';
1196
+ s.tools.forEach(function (t) {
1197
+ var zh = mcpToolZh(t.rawName, t.description);
1198
+ var label = zh || t.rawName;
1199
+ var sub = zh ? t.rawName : (t.description || "(无描述)");
1200
+ h += '<div class="dsb-mcp-tool" title="' + esc(t.description || "") + '"><b>' + esc(label) + '</b><span>' + esc(sub) + '</span></div>';
1201
+ });
1202
+ h += '</div>';
1203
+ } else {
1204
+ h += '<div style="color:var(--hintC,#9aa1b5)">未获取到已注册工具(可能未连接或已停用)</div>';
1205
+ }
1206
+ if (st && st.topTools && st.topTools.length) {
1207
+ h += '<div style="margin:8px 0 5px;font-weight:600">调用排行</div>';
1208
+ st.topTools.forEach(function (t) {
1209
+ h += '<div class="dsb-mcp-tool"><b>' + t.calls + '×</b><span>' + esc(String(t.tool).replace(/^mcp__[^_]+__/, "")) + '</span></div>';
1210
+ });
1211
+ }
1212
+ if (s.envKeys && s.envKeys.length) {
1213
+ h += '<div style="margin:8px 0 5px;font-weight:600">环境变量(值已打码)</div>';
1214
+ s.envKeys.forEach(function (k) {
1215
+ h += '<div class="dsb-mcp-tool"><b>' + esc(k) + '</b><span>' + esc(s.env[k]) + '</span></div>';
1216
+ });
1217
+ }
1218
+ h += '<div style="margin-top:6px;color:var(--hintC,#9aa1b5);font-size:10.5px">配置来源:' + esc(s.patchFile || "(内置层)") + '</div>';
1219
+ h += '</div>';
1220
+ return h;
1221
+ }
1222
+
1223
+ function mcpCardHtml(s, statsByName, M) {
1224
+ var st = statsByName[s.serverName] || null;
1225
+ var pill = mcpPill(s.phase);
1226
+ var target = s.transport === "stdio"
1227
+ ? (s.command + (s.args && s.args.length ? " " + s.args.join(" ") : ""))
1228
+ : s.url;
1229
+ var h = '<div class="dsb-mcp-card" data-mcp-id="' + esc(s.id) + '">';
1230
+ h += '<div class="dsb-mcp-hd"><div class="dsb-mcp-name" title="' + esc(s.serverName) + '">' + esc(s.serverName) + '</div><span class="dsb-mcp-transport">' + esc(s.transport) + '</span>' + (s.version ? '<span class="dsb-mcp-badge" title="从启动配置推断的版本">v' + esc(s.version) + '</span>' : '') + '</div>';
1231
+ h += '<div class="dsb-mcp-status"><span class="dsb-pill ' + pill.cls + '"><i class="dsb-pill-dot"></i>' + esc(pill.text) + '</span>';
1232
+ if (!s.disabled) h += '<span style="color:var(--hintC,#9aa1b5);font-size:11px">' + s.toolCount + ' 个工具</span>';
1233
+ h += '</div>';
1234
+ var tgt = target || "(未配置命令 / URL)";
1235
+ h += '<div class="dsb-mcp-target" title="' + esc(tgt) + '">' + esc(tgt.length > 90 ? tgt.slice(0, 90) + "…" : tgt) + '</div>';
1236
+ h += '<div class="dsb-mcp-summary">' + esc(s.summary) + '</div>';
1237
+ if (st) {
1238
+ h += '<div class="dsb-mcp-calls">累计调用 ' + st.calls + ' 次' + (st.fail ? '(失败 ' + st.fail + ')' : '') + (st.lastTs ? ' · 最近 ' + fmtMoment(st.lastTs) : '') + '</div>';
1239
+ } else {
1240
+ h += '<div class="dsb-mcp-calls">暂无调用记录</div>';
1241
+ }
1242
+ h += '<div class="dsb-mcp-acts">';
1243
+ h += '<label class="dsb-switch dsb-mcp-switch' + (s.disabled ? '' : ' dsb-sw-on') + '" data-stop="1" data-mcp-switch="' + esc(s.id) + '" data-on="' + (s.disabled ? '0' : '1') + '"' + (M.busy ? ' aria-disabled="true"' : '') + '>';
1244
+ h += '<span class="dsb-sw-track"><span class="dsb-sw-thumb"></span></span>';
1245
+ h += '<span class="dsb-sw-label">' + (s.disabled ? '关' : '开') + '</span>';
1246
+ h += '</label>';
1247
+ h += '<button class="dsb-mcp-btn" data-mcp-reconnect="' + esc(s.id) + '" ' + (s.disabled ? 'disabled' : '') + '>重连</button>';
1248
+ h += '<button class="dsb-mcp-btn" data-mcp-detail="' + esc(s.id) + '">' + (M.detailId === s.id ? '收起' : '详情') + '</button>';
1249
+ h += '<button class="dsb-mcp-btn" data-mcp-export="' + esc(s.id) + '">导出</button>';
1250
+ h += '</div>';
1251
+ if (M.detailId === s.id) h += mcpDetailHtml(s, st);
1252
+ h += '</div>';
1253
+ return h;
1254
+ }
1255
+
1256
+ function mcpPhaseColor(phase) {
1257
+ if (phase === "active") return "#34d399";
1258
+ if (phase === "failed") return "#f87171";
1259
+ if (phase === "loading" || phase === "pending" || phase === "unloading") return "#fbbf24";
1260
+ return "#9aa1b5";
1261
+ }
1262
+
1263
+ // v2.0.2 工具级统计取值与状态
1264
+ function mcpToolStat(ss, toolName) {
1265
+ if (!ss) return null;
1266
+ var name = String(toolName || "");
1267
+ var arr = ss.tools;
1268
+ if (arr && arr.length) {
1269
+ for (var i = 0; i < arr.length; i++) {
1270
+ var x = arr[i];
1271
+ if (x.tool === name) return x;
1272
+ if (x.tool && name && (x.tool.length >= name.length ? x.tool.slice(-name.length) === name : name.slice(-x.tool.length) === x.tool)) return x;
1273
+ }
1274
+ }
1275
+ var tt = ss.topTools || [];
1276
+ for (var j = 0; j < tt.length; j++) if (tt[j].tool === name) return { tool: tt[j].tool, calls: tt[j].calls, ok: null, fail: null, lastTs: 0 };
1277
+ return null;
1278
+ }
1279
+ function mcpToolState(ts) {
1280
+ if (!ts || !ts.calls) return { color: "#9aa1b5", text: "○ 未调用" };
1281
+ if (ts.ok == null) return { color: "#9aa1b5", text: "调用 " + ts.calls + " 次" };
1282
+ if (ts.fail === 0) return { color: "#34d399", text: "✓ 正常" };
1283
+ if (ts.ok === 0) return { color: "#f87171", text: "✗ 全失败" };
1284
+ return { color: "#fbbf24", text: "⚠ 部分失败" };
1285
+ }
1286
+
1287
+ // ── v2.0.3 MCP 故障一键送诊断 ──
1288
+ // DSH 对话框输入框是 Lexical contenteditable([data-lexical-editor="true"]),
1289
+ // 不能直接设 value,必须走 paste 事件(Lexical 自带 paste 解析)。
1290
+ function dsbFindComposer() {
1291
+ try {
1292
+ var els = document.querySelectorAll('[data-lexical-editor="true"]');
1293
+ var visible = null;
1294
+ for (var i = 0; i < els.length; i++) {
1295
+ var r = els[i].getBoundingClientRect();
1296
+ if (r.width > 40 && r.height > 10) { visible = els[i]; break; }
1297
+ }
1298
+ return visible || (els.length ? els[0] : null);
1299
+ } catch (e) { return null; }
1300
+ }
1301
+
1302
+ function dsbCopyText(text, okMsg) {
1303
+ try {
1304
+ if (navigator.clipboard && navigator.clipboard.writeText) {
1305
+ navigator.clipboard.writeText(text).then(function () {
1306
+ showToast("ok", okMsg || "已复制到剪贴板,按 Ctrl+V 粘贴到对话框");
1307
+ }).catch(function () {
1308
+ showToast("err", "复制失败,请手动复制");
1309
+ });
1310
+ return;
1311
+ }
1312
+ } catch (e) { /* 走下面兜底 */ }
1313
+ showToast("err", "剪贴板不可用,请手动复制");
1314
+ }
1315
+
1316
+ // 三级兜底:paste 事件 → execCommand insertText → 剪贴板
1317
+ function dsbSendToComposer(text) {
1318
+ var el = dsbFindComposer();
1319
+ if (!el) {
1320
+ dsbCopyText(text, "未找到对话框输入框,已复制(Ctrl+V 粘贴到对话框)");
1321
+ return false;
1322
+ }
1323
+ try { el.focus(); } catch (e) { }
1324
+ var probe = text.slice(0, 12);
1325
+ try {
1326
+ var dt = new DataTransfer();
1327
+ dt.setData("text/plain", text);
1328
+ var ev = new ClipboardEvent("paste", { clipboardData: dt, bubbles: true, cancelable: true });
1329
+ el.dispatchEvent(ev);
1330
+ } catch (e) { /* 走下面兜底 */ }
1331
+ if ((el.textContent || "").indexOf(probe) < 0) {
1332
+ try { document.execCommand("insertText", false, text); } catch (e2) { }
1333
+ }
1334
+ if ((el.textContent || "").indexOf(probe) >= 0) {
1335
+ showToast("ok", "诊断信息已填入对话框,按回车发送给 AI");
1336
+ return true;
1337
+ }
1338
+ dsbCopyText(text, "未能填入输入框,已复制(Ctrl+V 粘贴到对话框)");
1339
+ return false;
1340
+ }
1341
+
1342
+ function buildMcpDiag(srv, tool, ts) {
1343
+ var lines = [];
1344
+ lines.push("【MCP 工具故障诊断请求】");
1345
+ if (srv) {
1346
+ lines.push("MCP 服务器:" + srv.serverName + "(" + (srv.transport || "?") + ",状态 " + (srv.phase || "?") + ")");
1347
+ var cmd = srv.transport === "stdio" ? (srv.command || "") : (srv.url || "");
1348
+ if (cmd) lines.push("启动配置:" + cmd + (srv.args && srv.args.length ? " " + srv.args.join(" ") : ""));
1349
+ }
1350
+ if (tool) {
1351
+ var zh = mcpToolZh(tool.rawName, tool.description);
1352
+ lines.push("工具:" + tool.rawName + (zh ? "(" + zh + ")" : ""));
1353
+ if (tool.description) lines.push("工具说明:" + String(tool.description).slice(0, 200));
1354
+ }
1355
+ if (ts && ts.calls) {
1356
+ lines.push("调用情况:共 " + ts.calls + " 次 · 成功 " + (ts.ok == null ? "?" : ts.ok) + " · 失败 " + (ts.fail == null ? "?" : ts.fail) + (ts.avgDur ? " · 平均耗时 " + ts.avgDur + " ms" : ""));
1357
+ if (ts.lastTs) lines.push("最近调用:" + new Date(ts.lastTs).toLocaleString());
1358
+ if (ts.lastErr) lines.push("最近错误:" + ts.lastErr);
1359
+ } else {
1360
+ lines.push("调用情况:暂无调用记录");
1361
+ }
1362
+ lines.push("");
1363
+ lines.push("请帮我分析这个 MCP 工具为什么调用失败,并给出排查与修复建议。");
1364
+ return lines.join("\n");
1365
+ }
1366
+
1367
+ function handleMcpDiag(M, key) {
1368
+ var parts = String(key || "").split("::");
1369
+ var sid = parts[0], rawName = parts.slice(1).join("::");
1370
+ var srv = null;
1371
+ (M.data && M.data.servers ? M.data.servers : []).forEach(function (x) { if (x.id === sid) srv = x; });
1372
+ var tool = null;
1373
+ if (srv) (srv.tools || []).forEach(function (t) { if (t.rawName === rawName) tool = t; });
1374
+ var ss = null;
1375
+ if (srv && M.stats && M.stats.servers) M.stats.servers.forEach(function (x) { if (x.server === srv.serverName) ss = x; });
1376
+ var ts = mcpToolStat(ss, tool ? tool.name : rawName);
1377
+ dsbSendToComposer(buildMcpDiag(srv, tool, ts));
1378
+ }
1379
+
1380
+ // v2.0.2 树状视图:MCP 节点 → 工具节点 → 工具调用指标(三级大树)
1381
+ function mcpTreeHtml(s, statsByName, M) {
1382
+ var ss = statsByName[s.serverName] || null;
1383
+ var mExp = !!M.expanded[s.id];
1384
+ var target = s.transport === "stdio" ? (s.command + (s.args && s.args.length ? " " + s.args.join(" ") : "")) : s.url;
1385
+ var h = '<div class="dsb-mcp-node" data-mcp-id="' + esc(s.id) + '">';
1386
+ h += '<div class="dsb-mcp-row" data-mcp-toggle-node="' + esc(s.id) + '" title="' + esc(target || "") + '">';
1387
+ h += '<span class="dsb-mcp-caret' + (mExp ? ' open' : '') + '"></span>';
1388
+ h += '<i class="dsb-mcp-dot" style="background:' + mcpPhaseColor(s.phase) + '"></i>';
1389
+ h += '<span class="dsb-mcp-node-name">' + esc(s.serverName) + '</span>';
1390
+ h += '<span class="dsb-mcp-transport">' + esc(s.transport) + '</span>';
1391
+ if (s.version) h += '<span class="dsb-mcp-badge" title="从启动配置推断的版本">v' + esc(s.version) + '</span>';
1392
+ h += '<span class="dsb-mcp-badge">' + (s.disabled ? "已停用" : s.toolCount + " 个工具") + '</span>';
1393
+ if (ss && ss.calls) h += '<span class="dsb-mcp-badge">累计 ' + ss.calls + ' 次</span>';
1394
+ if (ss && ss.calls) h += '<span class="dsb-mcp-toolstate" style="color:' + (ss.fail ? "#fbbf24" : "#34d399") + '">' + (ss.fail ? "⚠ " + ss.fail + " 失败" : "✓ 全成功") + '</span>';
1395
+ h += '<span style="flex:1"></span>';
1396
+ h += '<label class="dsb-switch dsb-mcp-switch' + (s.disabled ? '' : ' dsb-sw-on') + '" data-stop="1" data-mcp-switch="' + esc(s.id) + '" data-on="' + (s.disabled ? '0' : '1') + '"' + (M.busy ? ' aria-disabled="true"' : '') + ' title="' + (s.disabled ? '点击启用' : '点击停用') + '">';
1397
+ h += '<span class="dsb-sw-track"><span class="dsb-sw-thumb"></span></span>';
1398
+ h += '<span class="dsb-sw-label">' + (s.disabled ? '关' : '开') + '</span>';
1399
+ h += '</label>';
1400
+ h += '<button class="dsb-mcp-btn" data-stop="1" data-mcp-reconnect="' + esc(s.id) + '" ' + (s.disabled ? 'disabled' : '') + '>重连</button>';
1401
+ h += '<button class="dsb-mcp-btn" data-stop="1" data-mcp-export="' + esc(s.id) + '">导出</button>';
1402
+ h += '</div>';
1403
+ h += '<div class="dsb-mcp-children" style="display:' + (mExp ? 'block' : 'none') + '">';
1404
+ h += '<div class="dsb-mcp-summary">' + esc(s.summary) + '</div>';
1405
+ if (s.tools && s.tools.length) {
1406
+ h += '<div class="dsb-mcp-sub">工具清单(' + s.tools.length + ')</div>';
1407
+ s.tools.forEach(function (t) {
1408
+ var ts = mcpToolStat(ss, t.name);
1409
+ var state = mcpToolState(ts);
1410
+ var zh = mcpToolZh(t.rawName, t.description);
1411
+ var tkey = s.id + "::" + t.rawName;
1412
+ var tExp = !!M.expanded[tkey];
1413
+ h += '<div class="dsb-mcp-toolnode">';
1414
+ h += '<div class="dsb-mcp-toolrow" data-mcp-tool-node="' + esc(tkey) + '" title="' + esc(t.description || "") + '">';
1415
+ h += '<span class="dsb-mcp-caret dsb-mcp-caret-s' + (tExp ? ' open' : '') + '"></span>';
1416
+ h += '<b class="dsb-mcp-toolname">' + esc(zh || t.rawName) + '</b>';
1417
+ h += '<span class="dsb-mcp-toolen">' + esc(t.rawName) + '</span>';
1418
+ h += '<span style="flex:1"></span>';
1419
+ if (ts && ts.calls) h += '<span class="dsb-mcp-badge">' + ts.calls + ' 次</span>';
1420
+ h += '<span class="dsb-mcp-toolstate" style="color:' + state.color + '">' + state.text + '</span>';
1421
+ if (ts && ts.lastTs) h += '<span class="dsb-mcp-badge">' + fmtMoment(ts.lastTs) + '</span>';
1422
+ if (ts && ts.fail > 0) h += '<button class="dsb-mcp-btn dsb-mcp-diag" data-stop="1" data-mcp-diag="' + esc(tkey) + '" title="把这个工具的故障信息直接填进对话框,让 AI 诊断">诊断</button>';
1423
+ h += '</div>';
1424
+ h += '<div class="dsb-mcp-toolchildren" style="display:' + (tExp ? 'block' : 'none') + '">';
1425
+ if (ts && ts.ok != null) {
1426
+ h += '<div class="dsb-mcp-kv"><span>成功</span><b style="color:#34d399">' + ts.ok + '</b></div>';
1427
+ h += '<div class="dsb-mcp-kv"><span>失败</span><b style="color:#f87171">' + ts.fail + '</b></div>';
1428
+ h += '<div class="dsb-mcp-kv"><span>平均耗时</span><b>' + (ts.avgDur ? ts.avgDur + ' ms' : '—') + '</b></div>';
1429
+ if (ts.lastErr) h += '<div class="dsb-mcp-kv"><span>最近错误</span><b style="color:#f87171;font-weight:400">' + esc(ts.lastErr) + '</b></div>';
1430
+ } else {
1431
+ h += '<div class="dsb-mcp-kv"><span>状态</span><b>该工具暂无调用记录</b></div>';
1432
+ }
1433
+ h += '</div>';
1434
+ h += '</div>';
1435
+ });
1436
+ } else {
1437
+ h += '<div class="dsb-mcp-sub2">未获取到已注册工具(可能未连接或已停用)</div>';
1438
+ }
1439
+ if (s.envKeys && s.envKeys.length) {
1440
+ h += '<div class="dsb-mcp-sub">环境变量(值已打码)</div>';
1441
+ s.envKeys.forEach(function (k) {
1442
+ h += '<div class="dsb-mcp-tool"><b>' + esc(k) + '</b><span>' + esc(s.env[k]) + '</span></div>';
1443
+ });
1444
+ }
1445
+ h += '<div class="dsb-mcp-src">配置来源:' + esc(s.patchFile || "(内置层)") + '</div>';
1446
+ h += '</div>';
1447
+ h += '</div>';
1448
+ return h;
1449
+ }
1450
+
1451
+ // 根节点:全部 MCP(树根)
1452
+ function mcpTreeRootHtml(data, statsByName, M) {
1453
+ var rootExp = !!M.expanded["__ROOT__"];
1454
+ var toolSum = (data.servers || []).reduce(function (a, s) { return a + (s.toolCount || 0); }, 0);
1455
+ var totalCalls = 0;
1456
+ (data.servers || []).forEach(function (s) { var ss = statsByName[s.serverName]; if (ss) totalCalls += (ss.calls || 0); });
1457
+ var h = '<div class="dsb-mcp-node dsb-mcp-rootnode">';
1458
+ h += '<div class="dsb-mcp-row dsb-mcp-rootrow" data-mcp-toggle-node="__ROOT__">';
1459
+ h += '<span class="dsb-mcp-caret' + (rootExp ? ' open' : '') + '"></span>';
1460
+ h += '<span class="dsb-mcp-rootname">全部 MCP</span>';
1461
+ h += '<span class="dsb-mcp-badge">' + data.total + ' 个服务器</span>';
1462
+ h += '<span class="dsb-mcp-badge">已连接 ' + data.active + '</span>';
1463
+ h += '<span class="dsb-mcp-badge">工具 ' + toolSum + '</span>';
1464
+ if (totalCalls) h += '<span class="dsb-mcp-badge">累计 ' + totalCalls + ' 次</span>';
1465
+ h += '</div>';
1466
+ h += '<div class="dsb-mcp-children" style="display:' + (rootExp ? 'block' : 'none') + '">';
1467
+ (data.servers || []).forEach(function (s) { h += mcpTreeHtml(s, statsByName, M); });
1468
+ h += '</div>';
1469
+ h += '</div>';
1470
+ return h;
1471
+ }
1472
+
1473
+ var _mcpPaintTimer = null;
1474
+ // v2.0.4 性能:合并重绘(16ms 内多次请求只渲染一次),避免频繁操作时整树重排卡顿。
1475
+ function paintMcp() {
1476
+ if (_mcpPaintTimer) return;
1477
+ _mcpPaintTimer = setTimeout(function () { _mcpPaintTimer = null; paintMcpNow(); }, 16);
1478
+ }
1479
+ function paintMcpNow() {
1480
+ var host = $("dsb-mcp"); if (!host) return;
1481
+ var M = mcpEnsure();
1482
+ var h = "";
1483
+ h += '<div class="dsb-mcp-top">';
1484
+ if (M.data) {
1485
+ var toolSum = (M.data.servers || []).reduce(function (a, s) { return a + (s.toolCount || 0); }, 0);
1486
+ h += '<span class="dsb-mcp-sum">共 ' + M.data.total + ' 个 MCP · 已连接 ' + M.data.active + ' 个 · 工具 ' + toolSum + ' 个</span>';
1487
+ } else {
1488
+ h += '<span class="dsb-mcp-sum">' + (M.loading ? '加载中…' : '尚未加载') + '</span>';
1489
+ }
1490
+ h += '<span style="flex:1"></span>';
1491
+ var isTree = M.mode !== "card";
1492
+ h += '<button class="dsb-mcp-btn ' + (isTree ? 'primary' : '') + '" data-mcp-act="mode-tree">树状</button>';
1493
+ h += '<button class="dsb-mcp-btn ' + (!isTree ? 'primary' : '') + '" data-mcp-act="mode-card">卡片</button>';
1494
+ if (!isTree) {
1495
+ h += '<span style="font-size:11px;color:var(--hintC,#9aa1b5);margin-left:4px">每行</span>';
1496
+ [0, 2, 3, 4, 5, 6].forEach(function (nn) {
1497
+ h += '<button class="dsb-mcp-btn ' + (M.cols === nn ? 'primary' : '') + '" data-mcp-cols="' + nn + '" title="' + (nn ? ("每行 " + nn + " 个") : "自适应") + '">' + (nn || '自') + '</button>';
1498
+ });
1499
+ }
1500
+ if (isTree && M.data && M.data.servers && M.data.servers.length) {
1501
+ h += '<button class="dsb-mcp-btn" data-mcp-act="expandAll">全部展开</button>';
1502
+ h += '<button class="dsb-mcp-btn" data-mcp-act="collapseAll">全部收起</button>';
1503
+ }
1504
+ h += '<button class="dsb-mcp-btn" data-mcp-act="refresh">刷新</button>';
1505
+ h += '<button class="dsb-mcp-btn" data-mcp-act="exportAll">导出全部</button>';
1506
+ h += '<button class="dsb-mcp-btn" data-mcp-act="clearStats">清空统计</button>';
1507
+ h += '<button class="dsb-mcp-btn" data-mcp-act="cleanResidue" title="检测并清除指向已卸载 MCP 的残留配置">清理残留</button>';
1508
+ h += '<button class="dsb-mcp-btn" data-mcp-act="scanLocal" title="扫描本机已安装 / 已配置的全部 MCP(只读)">🔍 本机 MCP</button>';
1509
+ h += '<button class="dsb-mcp-btn" data-mcp-act="aiFix" title="AI 诊断异常的 MCP 并给出/应用修复方案">🛠 AI 修复</button>';
1510
+ h += '</div>';
1511
+
1512
+ if (M.showExport) {
1513
+ h += '<div style="margin-bottom:10px">' +
1514
+ '<div style="font-size:11.5px;color:var(--hintC,#9aa1b5);margin-bottom:5px">mcpServers 配置(可直接粘贴到 Claude Desktop / Cursor)</div>' +
1515
+ '<textarea class="dsb-mcp-export" id="dsb-mcp-export-ta" readonly>' + esc(M.exportText) + '</textarea>' +
1516
+ '<div style="margin-top:6px;display:flex;gap:6px"><button class="dsb-mcp-btn primary" data-mcp-act="copyExport">复制</button><button class="dsb-mcp-btn" data-mcp-act="closeExport">关闭</button></div></div>';
1517
+ }
1518
+
1519
+ if (M.showResidue) {
1520
+ var rl = (M.residue && M.residue.length) ? M.residue : [];
1521
+ h += '<div class="dsb-mcp-residue">';
1522
+ if (!rl.length) {
1523
+ h += '<div style="font-size:12px;color:var(--hintC,#9aa1b5);padding:4px 0">没有检测到残留,配置是干净的。</div>';
1524
+ h += '<div style="margin-top:6px"><button class="dsb-mcp-btn" data-mcp-act="cancelResidue">关闭</button></div>';
1525
+ } else {
1526
+ h += '<div style="font-weight:600;font-size:12px;margin-bottom:6px;color:#fbbf24">检测到 ' + rl.length + ' 处残留(指向已卸载的 MCP,清掉只消除日志告警,不影响其它配置)</div>';
1527
+ rl.forEach(function (x, idx) {
1528
+ h += '<div class="dsb-mcp-residue-item">' + (idx + 1) + '. ' + esc(x.reason) + ' — id: ' + esc((x.ids || []).join(", ")) + (x.pkg ? '(包 ' + esc(x.pkg) + ')' : '') + '</div>';
1529
+ });
1530
+ h += '<div style="margin-top:8px;display:flex;gap:6px"><button class="dsb-mcp-btn primary" data-mcp-act="doCleanResidue">确认清理</button><button class="dsb-mcp-btn" data-mcp-act="cancelResidue">取消</button></div>';
1531
+ }
1532
+ h += '</div>';
1533
+ }
1534
+
1535
+ if (M.error) {
1536
+ h += '<div class="dsb-mcp-empty">加载失败:' + esc(M.error) + '<br><br><button class="dsb-mcp-btn" data-mcp-act="refresh">重试</button></div>';
1537
+ } else if (!M.data) {
1538
+ h += '<div class="dsb-mcp-empty">' + (M.loading ? '正在读取运行时 MCP 配置…' : '点击「刷新」加载') + '</div>';
1539
+ } else if (!M.data.servers || !M.data.servers.length) {
1540
+ h += '<div class="dsb-mcp-empty">当前没有挂载任何 MCP 服务器<br><span style="font-size:11px">可在 profile 的 cordis.patch.yml 里以 @deepseek-ai/dsh-mcp-client 挂载</span></div>';
1541
+ } else {
1542
+ var byName = {};
1543
+ if (M.stats && M.stats.servers) M.stats.servers.forEach(function (x) { byName[x.server] = x; });
1544
+ if (M.mode === "card") {
1545
+ h += '<div class="dsb-mcp-grid"' + (M.cols ? ' style="grid-template-columns:repeat(' + M.cols + ',minmax(0,1fr))"' : '') + '>';
1546
+ M.data.servers.forEach(function (s) { h += mcpCardHtml(s, byName, M); });
1547
+ h += '</div>';
1548
+ } else {
1549
+ h += '<div class="dsb-mcp-tree">';
1550
+ h += mcpTreeRootHtml(M.data, byName, M);
1551
+ h += '</div>';
1552
+ }
1553
+ if (M.stats) {
1554
+ var today = (M.stats.hourly || []).reduce(function (a, b) { return a + b; }, 0);
1555
+ h += '<div style="font-size:11px;color:var(--hintC,#9aa1b5);padding-top:8px">近 24 小时调用 ' + today + ' 次 · 累计 ' + (M.stats.total ? M.stats.total.calls : 0) + ' 次</div>';
1556
+ }
1557
+ }
1558
+ host.innerHTML = h;
1559
+ }
1560
+
1561
+ function openMcpFixMenu(issues) {
1562
+ var panel = $(PANEL_ID); if (!panel) return;
1563
+ var old = panel.querySelector(".dsb-scan"); if (old && old.parentNode) old.parentNode.removeChild(old);
1564
+ var rows = "";
1565
+ issues.forEach(function (x) {
1566
+ rows += '<div class="dsb-scan-row"><b>' + esc(x.serverName) + '</b>' +
1567
+ ' <span class="dsb-scan-dim">' + esc(x.symptoms || "") + '</span>' +
1568
+ '<div class="dsb-scan-desc">建议:<b>' + esc(x.action) + '</b> · ' + esc(x.reason || "") + '</div></div>';
1569
+ });
1570
+ var wrap = document.createElement("div");
1571
+ wrap.className = "dsb-scan";
1572
+ wrap.innerHTML = '<div class="dsb-scan-box"><div class="dsb-scan-h"><span>🛠 MCP AI 修复 · 诊断结果</span><span class="dsb-scan-close" data-scan-close="1">✕</span></div>' +
1573
+ '<div class="dsb-scan-list">' + rows + '</div>' +
1574
+ '<div style="padding:10px 16px;border-top:1px solid rgba(255,255,255,.12);display:flex;justify-content:flex-end;gap:8px">' +
1575
+ '<button class="dsb-btn" data-scan-close="1">取消</button>' +
1576
+ '<button class="dsb-btn" data-fix-apply="1">应用这些修复</button></div></div>';
1577
+ panel.appendChild(wrap);
1578
+ wrap.addEventListener("click", function (ev) {
1579
+ if (ev.target.closest("[data-scan-close]") || ev.target === wrap) { if (wrap.parentNode) wrap.parentNode.removeChild(wrap); return; }
1580
+ if (ev.target.closest("[data-fix-apply]")) {
1581
+ var actions = issues.map(function (x) { return { serverName: x.serverName, action: x.action }; });
1582
+ var btn = ev.target.closest("[data-fix-apply]"); btn.disabled = true; btn.textContent = "修复中…";
1583
+ fetch(apiBase() + "/mcp/ai-fix/apply", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ actions: actions }) })
1584
+ .then(function (r) { return r.json(); }).then(function (d) {
1585
+ if (wrap.parentNode) wrap.parentNode.removeChild(wrap);
1586
+ var okN = (d && d.results ? d.results.filter(function (x) { return x.ok; }).length : 0);
1587
+ var totalN = (d && d.results ? d.results.length : 0);
1588
+ showToast(okN ? "ok" : "err", "修复完成:" + okN + "/" + totalN + " 项成功");
1589
+ loadMcp(true);
1590
+ }).catch(function (e) { btn.disabled = false; btn.textContent = "应用这些修复"; showToast("err", "修复失败:" + ((e && e.message) || e)); });
1591
+ }
1592
+ });
1593
+ }
1594
+ function doMcpAiFix() {
1595
+ showToast("ok", "AI 诊断中…");
1596
+ fetch(apiBase() + "/mcp/ai-diagnose").then(function (r) { return r.json(); }).then(function (d) {
1597
+ if (!d || !d.ok) { showToast("err", "诊断失败:" + ((d && d.error) || "unknown")); return; }
1598
+ if (!d.issues || !d.issues.length) { showToast("ok", d.note || "没有发现异常的 MCP"); return; }
1599
+ openMcpFixMenu(d.issues);
1600
+ }).catch(function (e) { showToast("err", "诊断失败:" + ((e && e.message) || e)); });
1601
+ }
1602
+
1603
+ function openMcpScanMenu(rowsHtml) {
1604
+ var panel = $(PANEL_ID); if (!panel) return;
1605
+ var old = panel.querySelector(".dsb-scan"); if (old && old.parentNode) old.parentNode.removeChild(old);
1606
+ var wrap = document.createElement("div");
1607
+ wrap.className = "dsb-scan";
1608
+ wrap.innerHTML = '<div class="dsb-scan-box"><div class="dsb-scan-h"><span>🔍 本机 MCP 扫描</span><span class="dsb-scan-close" data-scan-close="1">✕</span></div>' +
1609
+ '<div class="dsb-scan-list">' + rowsHtml + '</div></div>';
1610
+ panel.appendChild(wrap);
1611
+ wrap.addEventListener("click", function (ev) {
1612
+ if (ev.target.closest("[data-scan-close]") || ev.target === wrap) { if (wrap.parentNode) wrap.parentNode.removeChild(wrap); }
1613
+ });
1614
+ }
1615
+ function doMcpScanLocal() {
1616
+ showToast("ok", "正在扫描本机 MCP…");
1617
+ fetch(apiBase() + "/mcp/local-scan").then(function (r) { return r.json(); }).then(function (d) {
1618
+ if (!d || !d.ok) { showToast("err", "扫描失败:" + ((d && d.error) || "unknown")); return; }
1619
+ var conf = d.configured || [];
1620
+ var inst = d.installed || [];
1621
+ var notConf = inst.filter(function (x) { return !x.configured; });
1622
+ var rows = "";
1623
+ rows += '<div class="dsb-scan-sec">已配置的 MCP(' + conf.length + ' 个)</div>';
1624
+ if (!conf.length) rows += '<div class="dsb-scan-dim">(无)</div>';
1625
+ conf.forEach(function (c) {
1626
+ rows += '<div class="dsb-scan-row"><b>' + esc(c.serverName) + '</b>' +
1627
+ ' <span class="dsb-scan-dim">' + esc(c.transport || "") + (c.version ? " · v" + esc(c.version) : "") + '</span>' +
1628
+ (c.disabled ? ' <span class="dsb-scan-off">已停用</span>' : ' <span class="dsb-scan-on">启用</span>') +
1629
+ ' <span class="dsb-scan-dim">工具 ' + (c.toolCount || 0) + ' 个</span></div>';
1630
+ });
1631
+ rows += '<div class="dsb-scan-sec">本机已装但未配置(' + notConf.length + ' 个)</div>';
1632
+ if (!notConf.length) rows += '<div class="dsb-scan-dim">没有发现未配置的 MCP 包</div>';
1633
+ notConf.forEach(function (x) {
1634
+ rows += '<div class="dsb-scan-row"><b>' + esc(x.name) + '</b>' +
1635
+ (x.version ? ' <span class="dsb-scan-dim">v' + esc(x.version) + '</span>' : '') +
1636
+ (x.bin ? ' <span class="dsb-scan-dim">bin: ' + esc(x.bin) + '</span>' : '') +
1637
+ '<div class="dsb-scan-desc">' + esc(x.description || "(无描述)") + '</div></div>';
1638
+ });
1639
+ openMcpScanMenu(rows);
1640
+ }).catch(function (e) { showToast("err", "扫描失败:" + ((e && e.message) || e)); });
1641
+ }
1642
+
1643
+ function handleMcpClick(ev) {
1644
+ var M = mcpEnsure();
1645
+ var b = ev.target.closest("[data-mcp-act]");
1646
+ if (b) {
1647
+ var act = b.getAttribute("data-mcp-act");
1648
+ if (act === "refresh") { loadMcp(true); return; }
1649
+ if (act === "exportAll") { doMcpExport(null); return; }
1650
+ if (act === "clearStats") { doMcpClearStats(); return; }
1651
+ if (act === "copyExport") { copyMcpExport(); return; }
1652
+ if (act === "closeExport") { M.showExport = false; paintMcp(); return; }
1653
+ if (act === "mode-tree" || act === "mode-card") {
1654
+ M.mode = (act === "mode-card") ? "card" : "tree";
1655
+ try { localStorage.setItem("dsb-mcp-mode", M.mode); } catch (e) { }
1656
+ paintMcp(); return;
1657
+ }
1658
+ if (act === "expandAll") {
1659
+ M.expanded = { "__ROOT__": true };
1660
+ (M.data && M.data.servers ? M.data.servers : []).forEach(function (s) {
1661
+ M.expanded[s.id] = true;
1662
+ (s.tools || []).forEach(function (t) { M.expanded[s.id + "::" + t.rawName] = true; });
1663
+ });
1664
+ paintMcp(); return;
1665
+ }
1666
+ if (act === "collapseAll") { M.expanded = { "__ROOT__": true }; paintMcp(); return; }
1667
+ if (act === "cleanResidue") { doDetectResidue(); return; }
1668
+ if (act === "doCleanResidue") { doCleanResidue(); return; }
1669
+ if (act === "cancelResidue") { M.showResidue = false; M.residue = null; paintMcp(); return; }
1670
+ if (act === "scanLocal") { doMcpScanLocal(); return; }
1671
+ if (act === "aiFix") { doMcpAiFix(); return; }
1672
+ }
1673
+ // 树状:点行头展开 / 收起(点带 data-stop 的操作控件时不切换)
1674
+ var nd = ev.target.closest("[data-mcp-toggle-node]");
1675
+ if (nd && !ev.target.closest("[data-stop]")) {
1676
+ var nid = nd.getAttribute("data-mcp-toggle-node");
1677
+ M.expanded[nid] = !M.expanded[nid];
1678
+ paintMcp(); return;
1679
+ }
1680
+ var mc = ev.target.closest("[data-mcp-cols]");
1681
+ if (mc) {
1682
+ M.cols = parseInt(mc.getAttribute("data-mcp-cols"), 10) || 0;
1683
+ try { localStorage.setItem("dsb-mcp-cols", String(M.cols)); } catch (e) { }
1684
+ paintMcp(); return;
1685
+ }
1686
+ var sw = ev.target.closest("[data-mcp-switch]");
1687
+ if (sw) {
1688
+ doMcpToggle(sw.getAttribute("data-mcp-switch"), sw.getAttribute("data-on") !== "1");
1689
+ return;
1690
+ }
1691
+ var dg = ev.target.closest("[data-mcp-diag]");
1692
+ if (dg) {
1693
+ handleMcpDiag(M, dg.getAttribute("data-mcp-diag"));
1694
+ return;
1695
+ }
1696
+ var tn = ev.target.closest("[data-mcp-tool-node]");
1697
+ if (tn && !ev.target.closest("[data-stop]")) {
1698
+ var tkey = tn.getAttribute("data-mcp-tool-node");
1699
+ M.expanded[tkey] = !M.expanded[tkey];
1700
+ paintMcp(); return;
1701
+ }
1702
+ var rc = ev.target.closest("[data-mcp-reconnect]");
1703
+ if (rc) { doMcpReconnect(rc.getAttribute("data-mcp-reconnect")); return; }
1704
+ var dt = ev.target.closest("[data-mcp-detail]");
1705
+ if (dt) { var id = dt.getAttribute("data-mcp-detail"); M.detailId = (M.detailId === id ? null : id); paintMcp(); return; }
1706
+ var ex = ev.target.closest("[data-mcp-export]");
1707
+ if (ex) { doMcpExport(ex.getAttribute("data-mcp-export")); return; }
1708
+ }
1709
+
1710
+ function handleMcpChange(ev) {
1711
+ var t = ev.target.closest("[data-mcp-toggle]");
1712
+ if (!t) return;
1713
+ doMcpToggle(t.getAttribute("data-mcp-toggle"), t.checked);
1714
+ }
1715
+
1716
+ function doMcpToggle(id, enable) {
1717
+ var M = mcpEnsure();
1718
+ if (M.busy) return;
1719
+ M.busy = id; paintMcp();
1720
+ fetch(apiBase() + "/mcp/" + (enable ? "enable" : "disable"), {
1721
+ method: "POST", headers: { "Content-Type": "application/json" },
1722
+ body: JSON.stringify({ id: id })
1723
+ }).then(function (r) { return r.json(); }).then(function (d) {
1724
+ M.busy = "";
1725
+ if (!d || !d.ok) { showToast("err", "操作失败:" + ((d && d.error) || "未知错误")); loadMcp(true); return; }
1726
+ showToast("ok", enable ? "已启用(配置已写入)" : "已停用(配置已写入)");
1727
+ setTimeout(function () { loadMcp(true); }, 900);
1728
+ paintMcp();
1729
+ }).catch(function (e) { M.busy = ""; showToast("err", "操作失败:" + ((e && e.message) || e)); paintMcp(); });
1730
+ }
1731
+
1732
+ function doMcpReconnect(id) {
1733
+ var M = mcpEnsure();
1734
+ if (M.busy) return;
1735
+ M.busy = id; paintMcp();
1736
+ fetch(apiBase() + "/mcp/reconnect", {
1737
+ method: "POST", headers: { "Content-Type": "application/json" },
1738
+ body: JSON.stringify({ id: id })
1739
+ }).then(function (r) { return r.json(); }).then(function (d) {
1740
+ M.busy = "";
1741
+ if (!d || !d.ok) { showToast("err", "重连失败:" + ((d && d.error) || "未知错误")); paintMcp(); return; }
1742
+ showToast("ok", "已触发重连");
1743
+ setTimeout(function () { loadMcp(true); }, 900);
1744
+ paintMcp();
1745
+ }).catch(function (e) { M.busy = ""; showToast("err", "重连失败:" + ((e && e.message) || e)); paintMcp(); });
1746
+ }
1747
+
1748
+ function doMcpClearStats() {
1749
+ var M = mcpEnsure();
1750
+ M.statsLoading = true; paintMcp();
1751
+ fetch(apiBase() + "/mcp/stats/clear", { method: "POST" }).then(function (r) { return r.json(); }).then(function (d) {
1752
+ M.statsLoading = false;
1753
+ if (d && d.ok) { M.stats = null; showToast("ok", "统计已清空"); loadMcp(true); }
1754
+ else { showToast("err", (d && d.error) || "清空失败"); paintMcp(); }
1755
+ }).catch(function (e) { M.statsLoading = false; showToast("err", "清空失败:" + ((e && e.message) || e)); paintMcp(); });
1756
+ }
1757
+
1758
+ function doMcpExport(oneId) {
1759
+ var M = mcpEnsure();
1760
+ fetch(apiBase() + "/mcp/export?mask=0").then(function (r) { return r.json(); }).then(function (d) {
1761
+ if (!d || !d.ok) { showToast("err", "导出失败:" + ((d && d.error) || "未知错误")); return; }
1762
+ var all = d.mcpServers || {};
1763
+ var out = all;
1764
+ if (oneId) {
1765
+ var s = null;
1766
+ if (M.data && M.data.servers) M.data.servers.forEach(function (x) { if (x.id === oneId) s = x; });
1767
+ var name = s ? s.serverName : oneId;
1768
+ out = {}; out[name] = all[name] || {};
1769
+ }
1770
+ M.exportText = JSON.stringify({ mcpServers: out }, null, 2);
1771
+ M.showExport = true;
1772
+ paintMcp();
1773
+ }).catch(function (e) { showToast("err", "导出失败:" + ((e && e.message) || e)); });
1774
+ }
1775
+
1776
+ function doDetectResidue() {
1777
+ var M = mcpEnsure();
1778
+ fetch(apiBase() + "/mcp/residue").then(function (r) { return r.json(); }).then(function (d) {
1779
+ if (!d || !d.ok) { showToast("err", "检测失败:" + ((d && d.error) || "未知错误")); return; }
1780
+ M.residue = d.residue || [];
1781
+ M.showResidue = true;
1782
+ paintMcp();
1783
+ }).catch(function (e) { showToast("err", "检测失败:" + ((e && e.message) || e)); });
1784
+ }
1785
+
1786
+ function doCleanResidue() {
1787
+ var M = mcpEnsure();
1788
+ fetch(apiBase() + "/mcp/residue/clean", { method: "POST" }).then(function (r) { return r.json(); }).then(function (d) {
1789
+ if (!d || !d.ok) { showToast("err", "清理失败:" + ((d && d.error) || "未知错误")); return; }
1790
+ M.showResidue = false; M.residue = null;
1791
+ showToast("ok", d.cleaned ? ("已清理 " + d.cleaned + " 处残留") : "没有残留可清理");
1792
+ loadMcp(true);
1793
+ }).catch(function (e) { showToast("err", "清理失败:" + ((e && e.message) || e)); });
1794
+ }
1795
+
1796
+ function copyMcpExport() {
1797
+ var M = mcpEnsure();
1798
+ var ta = $("dsb-mcp-export-ta");
1799
+ var txt = ta ? ta.value : (M.exportText || "");
1800
+ try {
1801
+ if (navigator.clipboard && navigator.clipboard.writeText) {
1802
+ navigator.clipboard.writeText(txt).then(function () { showToast("ok", "已复制到剪贴板"); }).catch(function () { fallbackCopy(ta, txt); });
1803
+ } else { fallbackCopy(ta, txt); }
1804
+ } catch (e) { fallbackCopy(ta, txt); }
1805
+ }
1806
+
1807
+ function fallbackCopy(ta, txt) {
1808
+ try {
1809
+ if (ta) { ta.removeAttribute("readonly"); ta.select(); document.execCommand("copy"); ta.setAttribute("readonly", "readonly"); }
1810
+ else { var t = document.createElement("textarea"); t.value = txt; document.body.appendChild(t); t.select(); document.execCommand("copy"); document.body.removeChild(t); }
1811
+ showToast("ok", "已复制到剪贴板");
1812
+ } catch (e) { showToast("err", "复制失败,请手动选中复制"); }
1813
+ }
1814
+
925
1815
  // 本地日期工具(避免时区漂移:全部用本地 Date 取 Y-M-D)
926
1816
  function ymd(d) {
927
1817
  var p = function (n) { return n < 10 ? "0" + n : String(n); };
@@ -1043,7 +1933,7 @@ window.__ModuleLoader__.load({
1043
1933
  if (!S.ledgerDetail || S.ledgerDetail.skill !== skill) return;
1044
1934
  var parts = [];
1045
1935
  if (d && d.ok) {
1046
- if (d.detail) parts.push("【人工台账失败明细】\n" + d.detail);
1936
+ if (d.detail) parts.push("【失败明细】\n" + d.detail);
1047
1937
  if (d.autoEntries && d.autoEntries.length) {
1048
1938
  var tot = (d.autoTotal == null ? d.autoEntries.length : d.autoTotal);
1049
1939
  var shownN = d.autoEntries.length;
@@ -1111,10 +2001,10 @@ window.__ModuleLoader__.load({
1111
2001
  '#dsh-skill-browser-panel .dsb-detail-body,#dsh-skill-browser-panel .dsb-groups{scrollbar-width:thin;' +
1112
2002
  'scrollbar-color:var(--dsb-brand,rgba(143,199,255,.75)) transparent;overscroll-behavior:contain}' +
1113
2003
  '.dsb-head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px 6px}' +
1114
- '.dsb-title{font-size:16px;font-weight:600}' +
2004
+ '.dsb-title{font-size:14.5px;font-weight:600}' +
1115
2005
  '.dsb-headbtns{display:flex;gap:6px}' +
1116
2006
  '.dsb-btn{background:rgba(255,255,255,.08);color:#dfe3ee;border:1px solid rgba(255,255,255,.12);border-radius:8px;' +
1117
- 'padding:4px 10px;font-size:12px;cursor:pointer}' +
2007
+ 'padding:3px 9px;font-size:11px;cursor:pointer}' +
1118
2008
  '.dsb-btn:hover{background:rgba(255,255,255,.16)}' +
1119
2009
  '.dsb-subtitle{padding:0 16px 6px;font-size:11px;color:#9aa1b5;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}' +
1120
2010
  '.dsb-settings{display:none;padding:6px 16px;gap:6px;flex-direction:column;background:rgba(255,255,255,.04);border-bottom:1px solid rgba(255,255,255,.06);margin-bottom:4px}' +
@@ -1131,22 +2021,22 @@ window.__ModuleLoader__.load({
1131
2021
  '.dsb-set-promo .p-desc{font-size:11.5px;color:#c6cbdb;line-height:1.5;margin-top:2px;white-space:normal}' +
1132
2022
  '.dsb-set-promo .p-tag{font-size:10.5px;color:#9aa1b5;margin-top:2px}' +
1133
2023
  '.dsb-q{margin:4px 16px 8px;padding:7px 12px;border-radius:9px;border:1px solid rgba(255,255,255,.14);' +
1134
- 'background:rgba(255,255,255,.06);color:#eef;font-size:13px;outline:none}' +
2024
+ 'background:rgba(255,255,255,.06);color:#eef;font-size:12px;outline:none}' +
1135
2025
  '.dsb-q:focus{border-color:#5b8cff}' +
1136
2026
  '.dsb-chips{display:flex;flex-wrap:wrap;gap:6px;padding:0 16px 6px}' +
1137
2027
  '.dsb-chip,.dsb-schip{background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.1);color:#cdd3e3;' +
1138
- 'border-radius:999px;padding:3px 10px;font-size:12px;cursor:pointer;white-space:nowrap}' +
2028
+ 'border-radius:999px;padding:2px 9px;font-size:11px;cursor:pointer;white-space:nowrap}' +
1139
2029
  '.dsb-chip:hover,.dsb-schip:hover{background:rgba(255,255,255,.14)}' +
1140
2030
  '.dsb-chip-on{background:linear-gradient(135deg,#5b6cff,#2fa8ff);color:#fff;border-color:transparent}' +
1141
- '.dsb-n{opacity:.75;font-size:11px;margin-left:2px}' +
2031
+ '.dsb-n{opacity:.75;font-size:10px;margin-left:2px}' +
1142
2032
  '.dsb-subs{display:none;flex-wrap:wrap;gap:6px;padding:0 16px 6px;border-top:1px dashed rgba(255,255,255,.08);padding-top:8px;margin:0 0 2px}' +
1143
2033
  '.dsb-cards{flex:1;min-height:0;overflow-y:auto;display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));' +
1144
2034
  'gap:8px;padding:6px 16px 14px;align-content:start}' +
1145
2035
  '.dsb-card{background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.08);border-radius:10px;' +
1146
- 'padding:10px 12px;cursor:pointer;transition:background .12s}' +
2036
+ 'padding:10px 12px;cursor:pointer;transition:background .12s;display:flex;flex-direction:column;height:100%;box-sizing:border-box}' +
1147
2037
  '.dsb-card:hover{background:rgba(120,140,255,.14);border-color:rgba(120,140,255,.4)}' +
1148
- '.dsb-card-name{font-family:ui-monospace,Consolas,monospace;font-size:12px;color:#8fc7ff;word-break:break-all}' +
1149
- '.dsb-card-zh{font-size:12.5px;color:#dfe3ee;margin-top:5px;line-height:1.5;' +
2038
+ '.dsb-card-name{font-family:ui-monospace,Consolas,monospace;font-size:11px;color:#8fc7ff;word-break:break-all}' +
2039
+ '.dsb-card-zh{font-size:11.5px;color:#dfe3ee;margin-top:5px;line-height:1.5;' +
1150
2040
  'display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}' +
1151
2041
  '.dsb-card-tag{font-size:10.5px;color:#8b93a8;margin-top:6px}' +
1152
2042
  '.dsb-empty{grid-column:1/-1;text-align:center;color:#9aa1b5;padding:40px 0;font-size:13px;line-height:1.7}' +
@@ -1155,9 +2045,18 @@ window.__ModuleLoader__.load({
1155
2045
  'font-family:ui-monospace,Consolas,monospace;font-size:12px;line-height:1.65;color:#cfd6e8;white-space:pre-wrap;word-break:break-word}' +
1156
2046
  '.dsb-tabs{display:flex;gap:4px;margin:0 8px}' +
1157
2047
  '.dsb-tab{background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.1);color:#cdd3e3;' +
1158
- 'border-radius:8px;padding:4px 12px;font-size:12px;cursor:pointer}' +
2048
+ 'border-radius:8px;padding:4px 12px;font-size:12px;cursor:pointer;display:inline-flex;align-items:center;gap:5px}' +
1159
2049
  '.dsb-tab:hover{background:rgba(255,255,255,.14)}' +
1160
2050
  '.dsb-tab-on{background:linear-gradient(135deg,#5b6cff,#2fa8ff);color:#fff;border-color:transparent}' +
2051
+ '.dsb-guide{display:none;flex:1;min-height:0;overflow:auto;padding:14px 18px;color:var(--ptx,#e8eaf2)}' +
2052
+ '.dsb-guide-inner{max-width:640px;margin:0 auto}' +
2053
+ '.dsb-guide h3{margin:0 0 4px;font-size:16px;font-weight:700;color:var(--ptx,#e8eaf2);display:flex;align-items:center;gap:7px}' +
2054
+ '.dsb-gico{display:inline-flex;align-items:center;margin-right:7px;color:var(--stA,#5b8cff);opacity:.95;vertical-align:-2px}' +
2055
+ '.dsb-gico svg{display:block}' +
2056
+ '.dsb-guide-sub{margin:0 0 12px;font-size:12px;color:var(--hintC,#9aa1b5)}' +
2057
+ '.dsb-guide-card{background:var(--chipBg,rgba(128,138,160,.07));border:1px solid var(--pbd,rgba(128,138,160,.25));border-radius:10px;padding:10px 12px;margin-bottom:8px;font-size:12.5px;line-height:1.7;color:var(--ptx,#e8eaf2)}' +
2058
+ '.dsb-guide-card b{color:var(--stA,#5b8cff);font-weight:700}' +
2059
+ '.dsb-guide-tip{font-size:11.5px;color:var(--hintC,#9aa1b5);margin:10px 0 0}' +
1161
2060
  '.dsb-ledger{flex:1;min-height:0;display:flex;flex-direction:column;padding:0 16px 14px}' +
1162
2061
  '.dsb-ledger-sum{display:flex;gap:10px;padding:4px 0 10px}' +
1163
2062
  '.dsb-lsum-card{flex:1;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.09);border-radius:10px;' +
@@ -1182,7 +2081,79 @@ window.__ModuleLoader__.load({
1182
2081
  '.dsb-lst-warn{color:#ffb35c}' +
1183
2082
  '.dsb-lst-ok{color:#9aa1b5}' +
1184
2083
  /* ── v1.7.0:排序切换 / 批量启停 / 单技能开关 / 停用视觉 ── */
1185
- '.dsb-sortbar{display:flex;align-items:center;gap:8px;padding:0 16px 6px}' +
2084
+ '.dsb-sortbar{display:flex;align-items:center;flex-wrap:wrap;gap:8px;padding:0 16px 6px}' +
2085
+ // ── v2.0.0 MCP 管理面板 ──
2086
+ '.dsb-mcp{flex:1;min-height:0;display:none;flex-direction:column;padding:0 16px 14px}' +
2087
+ '.dsb-mcp-top{display:flex;align-items:center;gap:8px;padding:4px 0 10px;flex-wrap:wrap}' +
2088
+ '.dsb-mcp-sum{font-size:12px;color:var(--hintC,#9aa1b5)}' +
2089
+ '.dsb-mcp-grid{flex:1;min-height:0;overflow-y:auto;display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:10px;align-content:start}' +
2090
+ // 树状视图(v2.0.1)
2091
+ '.dsb-mcp-tree{flex:1;min-height:0;overflow-y:auto;display:flex;flex-direction:column}' +
2092
+ '.dsb-mcp-node{border-bottom:1px solid rgba(128,138,160,.14)}' +
2093
+ '.dsb-mcp-row{display:flex;align-items:center;gap:7px;padding:7px 8px;border-radius:7px;cursor:pointer}' +
2094
+ '.dsb-mcp-row:hover{background:var(--chipBg,rgba(128,138,160,.10))}' +
2095
+ '.dsb-mcp-caret{width:14px;height:14px;flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;color:var(--hintC,#9aa1b5);transition:transform .15s ease;transform-origin:center}' +
2096
+ '.dsb-mcp-caret::before{content:"";width:0;height:0;border-left:5px solid currentColor;border-top:4px solid transparent;border-bottom:4px solid transparent;margin-left:1px}' +
2097
+ '.dsb-mcp-caret.open{transform:rotate(90deg)}' +
2098
+ '.dsb-mcp-dot{width:8px;height:8px;border-radius:50%;flex:0 0 auto}' +
2099
+ '.dsb-mcp-node-name{font-size:13.5px;font-weight:700;color:var(--ptx,#e8eaf2);flex:0 0 auto}' +
2100
+ '.dsb-mcp-badge{font-size:10.5px;color:var(--hintC,#9aa1b5);background:rgba(128,138,160,.14);border:1px solid var(--pbd,rgba(128,138,160,.22));padding:1px 7px;border-radius:20px;flex:0 0 auto}' +
2101
+ '.dsb-mcp-children{margin-left:20px;padding:2px 0 8px 12px;border-left:1px dashed var(--pbd,rgba(128,138,160,.3))}' +
2102
+ '.dsb-mcp-children .dsb-mcp-summary{margin:1px 0 6px}' +
2103
+ '.dsb-mcp-sub{margin:8px 0 4px;font-weight:600;font-size:11.5px;color:var(--ptx,#e8eaf2)}' +
2104
+ '.dsb-mcp-sub2{color:var(--hintC,#9aa1b5);font-size:11px;padding:2px 0 6px}' +
2105
+ '.dsb-mcp-src{margin-top:6px;color:var(--hintC,#9aa1b5);font-size:10.5px}' +
2106
+ '.dsb-mcp-tree-tools{display:flex;flex-direction:column;gap:1px}' +
2107
+ // 三级大树:根、工具节点、工具明细、树形连线(v2.0.2)
2108
+ '.dsb-mcp-rootrow{background:rgba(91,140,255,.10);border:1px solid rgba(91,140,255,.22);border-radius:8px;margin:3px 0}' +
2109
+ '.dsb-mcp-rootname{font-size:14px;font-weight:800;color:var(--stA,#5b8cff)}' +
2110
+ '.dsb-mcp-toolnode{position:relative}' +
2111
+ '.dsb-mcp-toolrow{display:flex;align-items:center;gap:6px;padding:4px 6px;border-radius:6px;cursor:pointer}' +
2112
+ '.dsb-mcp-toolrow:hover{background:var(--chipBg,rgba(128,138,160,.10))}' +
2113
+ '.dsb-mcp-caret-s{width:11px;height:11px}' +
2114
+ '.dsb-mcp-caret-s::before{border-left-width:4px;border-top-width:3px;border-bottom-width:3px;margin-left:0}' +
2115
+ '.dsb-mcp-toolname{font-weight:600;color:var(--ptx,#e8eaf2);font-size:12px;flex:0 1 auto;max-width:52%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}' +
2116
+ '.dsb-mcp-toolen{color:var(--hintC,#9aa1b5);font-size:10.5px;flex:0 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}' +
2117
+ '.dsb-mcp-toolstate{font-size:10.5px;font-weight:600;flex:0 0 auto}' +
2118
+ '.dsb-mcp-switch{flex:0 0 auto}' +
2119
+ '.dsb-mcp-switch .dsb-sw-label{font-size:11px}' +
2120
+ '.dsb-mcp-diag{color:#f87171;border-color:rgba(248,113,113,.4);background:rgba(248,113,113,.12);padding:2px 8px;font-size:10.5px}' +
2121
+ '.dsb-mcp-diag:hover{background:rgba(248,113,113,.24)}' +
2122
+ '.dsb-mcp-toolchildren{margin-left:16px;padding:3px 0 4px 10px;border-left:1px dashed var(--pbd,rgba(128,138,160,.3))}' +
2123
+ '.dsb-mcp-kv{display:flex;gap:10px;font-size:11px;padding:1px 0}' +
2124
+ '.dsb-mcp-kv span{color:var(--hintC,#9aa1b5);min-width:56px}' +
2125
+ '.dsb-mcp-kv b{color:var(--ptx,#e8eaf2);font-weight:600;word-break:break-all;min-width:0}' +
2126
+ '.dsb-mcp-children>.dsb-mcp-node{position:relative}' +
2127
+ '.dsb-mcp-children>.dsb-mcp-node::before{content:"";position:absolute;left:-13px;top:16px;width:11px;height:1px;background:var(--pbd,rgba(128,138,160,.35))}' +
2128
+ '.dsb-mcp-card{background:var(--chipBg,rgba(128,138,160,.07));border:1px solid var(--pbd,rgba(128,138,160,.25));border-radius:11px;padding:11px 12px;display:flex;flex-direction:column;gap:7px}' +
2129
+ '.dsb-mcp-hd{display:flex;align-items:center;gap:7px}' +
2130
+ '.dsb-mcp-name{font-size:14px;font-weight:700;color:var(--ptx,#e8eaf2);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}' +
2131
+ '.dsb-mcp-transport{font-size:10.5px;padding:1px 6px;border-radius:5px;background:rgba(91,140,255,.16);color:var(--stA,#5b8cff);border:1px solid rgba(91,140,255,.3);flex:0 0 auto}' +
2132
+ '.dsb-mcp-status{display:flex;align-items:center;gap:6px;font-size:11.5px}' +
2133
+ '.dsb-pill{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:20px;font-size:11px;font-weight:600;border:1px solid transparent}' +
2134
+ '.dsb-pill-dot{width:6px;height:6px;border-radius:50%;background:currentColor;flex:0 0 auto}' +
2135
+ '.dsb-pill-active{color:#34d399;background:rgba(52,211,153,.12);border-color:rgba(52,211,153,.3)}' +
2136
+ '.dsb-pill-failed{color:#f87171;background:rgba(248,113,113,.12);border-color:rgba(248,113,113,.3)}' +
2137
+ '.dsb-pill-warn{color:#fbbf24;background:rgba(251,191,36,.12);border-color:rgba(251,191,36,.3)}' +
2138
+ '.dsb-pill-off{color:#9aa1b5;background:rgba(154,161,181,.12);border-color:rgba(154,161,181,.28)}' +
2139
+ '.dsb-mcp-target{font-size:11px;color:var(--hintC,#9aa1b5);word-break:break-all;line-height:1.5}' +
2140
+ '.dsb-mcp-summary{font-size:11.5px;color:var(--ptx,#e8eaf2);line-height:1.55}' +
2141
+ '.dsb-mcp-calls{font-size:11px;color:var(--hintC,#9aa1b5)}' +
2142
+ '.dsb-mcp-acts{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:2px}' +
2143
+ '.dsb-mcp-btn{font-size:11px;padding:3px 9px;border-radius:6px;cursor:pointer;background:rgba(128,138,160,.14);border:1px solid var(--pbd,rgba(128,138,160,.3));color:var(--ptx,#e8eaf2);font-family:inherit}' +
2144
+ '.dsb-mcp-btn:hover{background:rgba(128,138,160,.26)}' +
2145
+ '.dsb-mcp-btn.primary{background:linear-gradient(135deg,#5b6cff,#2fa8ff);border-color:transparent;color:#fff}' +
2146
+ '.dsb-mcp-btn:disabled{opacity:.45;cursor:not-allowed}' +
2147
+ '.dsb-mcp-switch{display:inline-flex;align-items:center;gap:5px;font-size:11px;color:var(--ptx,#e8eaf2);cursor:pointer}' +
2148
+ '.dsb-mcp-detail{margin-top:4px;border-top:1px dashed var(--pbd,rgba(128,138,160,.3));padding-top:8px;font-size:11.5px}' +
2149
+ '.dsb-mcp-tools{max-height:180px;overflow:auto;display:flex;flex-direction:column;gap:3px}' +
2150
+ '.dsb-mcp-tool{display:flex;gap:8px;padding:2px 0;border-bottom:1px solid rgba(128,138,160,.1);align-items:baseline}' +
2151
+ '.dsb-mcp-tool b{font-weight:600;color:var(--stA,#5b8cff);font-size:11px;flex:0 0 auto;max-width:56%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}' +
2152
+ '.dsb-mcp-tool span{color:var(--hintC,#9aa1b5);font-size:10.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}' +
2153
+ '.dsb-mcp-empty{font-size:12px;color:var(--hintC,#9aa1b5);padding:24px;text-align:center;line-height:1.7}' +
2154
+ '.dsb-mcp-export{width:100%;min-height:160px;background:rgba(0,0,0,.3);border:1px solid var(--pbd,rgba(128,138,160,.3));border-radius:8px;color:var(--ptx,#e8eaf2);font-family:ui-monospace,Consolas,monospace;font-size:11px;padding:8px;resize:vertical;box-sizing:border-box}' +
2155
+ '.dsb-mcp-residue{background:rgba(251,191,36,.08);border:1px solid rgba(251,191,36,.3);border-radius:8px;padding:9px 11px;margin-bottom:10px}' +
2156
+ '.dsb-mcp-residue-item{font-size:11.5px;color:var(--ptx,#e8eaf2);line-height:1.6;padding:1px 0;word-break:break-all}' +
1186
2157
  '.dsb-sortbtn{background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);color:#cdd3e3;' +
1187
2158
  'border-radius:999px;padding:3px 12px;font-size:12px;cursor:pointer;white-space:nowrap}' +
1188
2159
  '.dsb-sortbtn:hover{background:rgba(255,255,255,.14)}' +
@@ -1256,6 +2227,12 @@ window.__ModuleLoader__.load({
1256
2227
  '#dsh-skill-browser-panel .dsb-bmenu-danger .dsb-bmenu-sub{color:#ff9d9d}' +
1257
2228
  '#dsh-skill-browser-panel .dsb-bmenu-cancel{background:transparent;border:none;color:#9aa1b5;font-size:12px;cursor:pointer;padding:4px 6px;text-align:center}' +
1258
2229
  '#dsh-skill-browser-panel .dsb-bmenu-cancel:hover{color:#dfe3ee}' +
2230
+ // v2.0.10 AI 分组方案弹层:两个操作按钮与背景明显区分,且随皮肤(主色用 --dsb-onbrand-bg)
2231
+ '#dsh-skill-browser-panel .dsb-plan-menu .dsb-bmenu-item{padding:9px 11px!important;font-weight:600;border:1px solid var(--pbd,rgba(255,255,255,.2))!important}' +
2232
+ '#dsh-skill-browser-panel .dsb-plan-menu .dsb-bmenu-item[data-plan-act="replace"]{background:linear-gradient(135deg,rgba(248,113,113,.30),rgba(248,113,113,.14))!important;border-color:rgba(248,113,113,.55)!important;color:#ffe2e2!important}' +
2233
+ '#dsh-skill-browser-panel .dsb-plan-menu .dsb-bmenu-item[data-plan-act="replace"]:hover{background:rgba(248,113,113,.38)!important}' +
2234
+ '#dsh-skill-browser-panel .dsb-plan-menu .dsb-bmenu-item[data-plan-act="merge"]{background:linear-gradient(135deg,var(--dsb-onbrand-bg1,#5b6cff),var(--dsb-onbrand-bg2,#2fa8ff))!important;border-color:transparent!important;color:#fff!important}' +
2235
+ '#dsh-skill-browser-panel .dsb-plan-menu .dsb-bmenu-item[data-plan-act="merge"]:hover{filter:brightness(1.08)}' +
1259
2236
  '#dsh-skill-browser-panel .dsb-lc-date{font-size:10.5px;color:#8b93a8;white-space:nowrap}' +
1260
2237
  '#dsh-skill-browser-panel .dsb-lc-rank{font-size:10.5px;color:#8b93a8;text-align:right;white-space:nowrap}' +
1261
2238
  '#dsh-skill-browser-panel .dsb-lc-status{font-size:10.5px;color:#ffb3b3;white-space:nowrap}' +
@@ -1336,15 +2313,20 @@ window.__ModuleLoader__.load({
1336
2313
  // v1.9.9:工具条/子类按钮的文字色一律走 --dsb-dim(该变量在 semanticVarsCss
1337
2314
  // 里按「卡片真实底色」算到 ≥4.8:1),不再用 --itm —— 实测 --itm 在 6 款皮肤上
1338
2315
  // 只有 4.40~4.50:1,10~11px 小字刚好卡在 WCAG AA 线下方。
1339
- '#dsh-skill-browser-panel .dsb-gadd{height:26px;min-width:0;border-radius:7px;border:1px dashed var(--pbd,rgba(255,255,255,.22));' +
1340
- 'background:var(--chipBg,rgba(255,255,255,.04));color:var(--dsb-dim,#b9c2d8);font-size:11px;cursor:pointer;padding:0 8px;' +
2316
+ '#dsh-skill-browser-panel .dsb-gadd{height:24px;min-width:0;border-radius:7px;border:1px dashed var(--pbd,rgba(255,255,255,.22));' +
2317
+ 'background:var(--chipBg,rgba(255,255,255,.04));color:var(--dsb-dim,#b9c2d8);font-size:10.5px;cursor:pointer;padding:0 7px;' +
1341
2318
  'display:inline-flex;align-items:center;gap:4px;white-space:nowrap}' +
1342
2319
  '#dsh-skill-browser-panel .dsb-gadd:hover{color:var(--ptx,#dfe3ee);border-color:var(--stA,#7c8dff)}' +
1343
2320
  '#dsh-skill-browser-panel .dsb-gadd-on{background:linear-gradient(135deg,var(--dsb-onbrand-bg1,#5b6cff),var(--dsb-onbrand-bg2,#2fa8ff))!important;' +
1344
2321
  'color:var(--dsb-onbrand,#fff)!important;border-style:solid!important;border-color:transparent!important}' +
1345
2322
  '#dsh-skill-browser-panel .dsb-gadd-kw{border-color:var(--dsb-okline,rgba(80,220,150,.34));color:var(--dsb-ok,#9ff5c8)}' +
1346
2323
  '#dsh-skill-browser-panel .dsb-gbusy{opacity:.55;pointer-events:none}' +
1347
- '#dsh-skill-browser-panel .dsb-gtitle{font-size:11px;color:var(--dsb-dim,#8b93a8);white-space:nowrap}' +
2324
+ '#dsh-skill-browser-panel .dsb-gtitle{font-size:10.5px;color:var(--dsb-dim,#8b93a8);white-space:nowrap}' +
2325
+ // v2.0.11 分组工具条美化:统一间距、按钮实心化、竖分隔线、选中态皮肤主色
2326
+ '#dsh-skill-browser-panel .dsb-gbar{gap:8px;row-gap:8px;padding:2px 0 8px}' +
2327
+ '#dsh-skill-browser-panel .dsb-gsep{width:1px;height:16px;background:var(--pbd,rgba(255,255,255,.18));margin:0 3px;flex:0 0 auto;border-radius:1px}' +
2328
+ '#dsh-skill-browser-panel .dsb-gbar .dsb-gadd{height:26px!important;padding:0 10px!important;border:1px solid var(--pbd,rgba(255,255,255,.2))!important;border-radius:8px!important;font-size:11px!important;line-height:1!important}' +
2329
+ '#dsh-skill-browser-panel .dsb-gbar .dsb-gadd-on{background:linear-gradient(135deg,var(--dsb-onbrand-bg1,#5b6cff),var(--dsb-onbrand-bg2,#2fa8ff))!important;color:#fff!important;border-color:transparent!important}' +
1348
2330
  '#dsh-skill-browser-panel .dsb-ghint{font-size:10.5px;color:var(--dsb-dim,#6f778c);white-space:nowrap;' +
1349
2331
  'overflow:hidden;text-overflow:ellipsis;max-width:44%}' +
1350
2332
  // v1.9.9 对比度修正:数字徽标一律「继承父级文字色」,不再写死
@@ -1353,7 +2335,7 @@ window.__ModuleLoader__.load({
1353
2335
  // 必有一处看不清 —— 截图里「全部 111」的白字就是这么来的。
1354
2336
  // 继承父色后:工具条→--itm、激活按钮→--dsb-onbrand、大类行→--dsb-oncard,
1355
2337
  // 三处都天然达标。
1356
- '#dsh-skill-browser-panel .dsb-gn{font-size:10px;color:inherit;opacity:.82;' +
2338
+ '#dsh-skill-browser-panel .dsb-gn{font-size:9.5px;color:inherit;opacity:.82;' +
1357
2339
  'flex:0 0 auto;font-weight:500}' +
1358
2340
  '#dsh-skill-browser-panel .dsb-gadd-on .dsb-gn{opacity:.9}' +
1359
2341
  '#dsh-skill-browser-panel .dsb-gcard .dsb-gn,' +
@@ -1362,7 +2344,7 @@ window.__ModuleLoader__.load({
1362
2344
  /* ── v1.9.8 平铺模式(原样保留,用户可一键切回)── */
1363
2345
  '#dsh-skill-browser-panel .dsb-gflat{display:flex;flex-wrap:wrap;gap:5px;align-items:center}' +
1364
2346
  '#dsh-skill-browser-panel .dsb-gcard,#dsh-skill-browser-panel .dsb-gcard-dom{position:relative;display:flex;align-items:center;gap:5px;' +
1365
- 'min-width:0;max-width:190px;height:30px;padding:0 9px;border-radius:8px;cursor:pointer;overflow:hidden;' +
2347
+ 'min-width:0;max-width:190px;height:27px;padding:0 8px;border-radius:8px;cursor:pointer;overflow:hidden;' +
1366
2348
  'background:linear-gradient(135deg,var(--dsb-tile1,#39406b),var(--dsb-tile2,#232a45));' +
1367
2349
  'border:1px solid var(--pbd,rgba(255,255,255,.18));background-size:cover;background-position:center}' +
1368
2350
  '#dsh-skill-browser-panel .dsb-gcard::after{content:"";position:absolute;inset:0;' +
@@ -1371,7 +2353,7 @@ window.__ModuleLoader__.load({
1371
2353
  '#dsh-skill-browser-panel .dsb-gcard:hover{border-color:var(--stA,#7c8dff)}' +
1372
2354
  '#dsh-skill-browser-panel .dsb-gcard-on{border-color:var(--stA,#7c8dff)!important;' +
1373
2355
  'box-shadow:0 0 0 2px var(--stA,#7c8dff) inset}' +
1374
- '#dsh-skill-browser-panel .dsb-gname{font-size:11.5px;color:var(--dsb-oncard,#fff);font-weight:600;' +
2356
+ '#dsh-skill-browser-panel .dsb-gname{font-size:11px;color:var(--dsb-oncard,#fff);font-weight:600;' +
1375
2357
  'line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:132px;' +
1376
2358
  'text-shadow:0 1px 2px rgba(0,0,0,.55)}' +
1377
2359
  '#dsh-skill-browser-panel .dsb-gkids{font-size:9.5px;color:var(--dsb-oncard-dim,#cfd8ea);' +
@@ -1384,6 +2366,30 @@ window.__ModuleLoader__.load({
1384
2366
  /* ── v1.9.3:AI 分组方案确认弹层 ── */
1385
2367
  '#dsh-skill-browser-panel .dsb-plan-menu{min-width:0;width:calc(100% - 24px);left:12px;overflow-y:auto}' +
1386
2368
  '#dsh-skill-browser-panel .dsb-plan-stat{font-size:10.5px;color:var(--dsb-dim,#8b93a8);padding:2px 8px 6px}' +
2369
+ '#dsh-skill-browser-panel .dsb-plan-time{font-size:11px;font-weight:600;color:var(--dsb-brand,#8fc7ff);padding:0 8px 8px}' +
2370
+ '#dsh-skill-browser-panel .dsb-plan-recache{background:transparent;border:1px dashed var(--pbd,rgba(255,255,255,.22));color:var(--dsb-dim,#9aa1b5);font-size:12px;padding:6px;border-radius:8px;cursor:pointer;margin:2px 0}' +
2371
+ '#dsh-skill-browser-panel .dsb-plan-recache:hover{border-color:var(--stA,#7c8dff);color:var(--ptx,#eef)}' +
2372
+ '#dsh-skill-browser-panel .dsb-ai-last{font-size:10.5px;color:var(--dsb-dim,#8b93a8);white-space:nowrap;flex:0 0 auto}' +
2373
+ '#dsh-skill-browser-panel .dsb-tprompt{position:absolute;inset:0;background:rgba(0,0,0,.5);z-index:70;display:flex;align-items:center;justify-content:center}' +
2374
+ '#dsh-skill-browser-panel .dsb-tprompt-box{background:#1f2430;border:1px solid rgba(255,255,255,.2);border-radius:10px;padding:16px;width:min(320px,80%);box-shadow:0 12px 32px rgba(0,0,0,.55)}' +
2375
+ '#dsh-skill-browser-panel .dsb-tprompt-title{font-size:12.5px;color:#e8ecf6;margin-bottom:10px}' +
2376
+ '#dsh-skill-browser-panel .dsb-tprompt-inp{width:100%;box-sizing:border-box;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.2);border-radius:6px;color:#eef;font-size:12.5px;padding:7px 9px;outline:none}' +
2377
+ '#dsh-skill-browser-panel .dsb-tprompt-ops{display:flex;justify-content:flex-end;gap:8px;margin-top:12px}' +
2378
+ '#dsh-skill-browser-panel .dsb-scan{position:absolute;inset:0;background:rgba(0,0,0,.55);z-index:70;display:flex;align-items:center;justify-content:center}' +
2379
+ '#dsh-skill-browser-panel .dsb-scan-box{background:#1f2430;border:1px solid rgba(255,255,255,.2);border-radius:10px;width:min(560px,90%);max-height:82%;display:flex;flex-direction:column;box-shadow:0 16px 40px rgba(0,0,0,.6)}' +
2380
+ '#dsh-skill-browser-panel .dsb-scan-h{font-size:13px;font-weight:700;color:#e8ecf6;padding:13px 16px;border-bottom:1px solid rgba(255,255,255,.12);display:flex;justify-content:space-between}' +
2381
+ '#dsh-skill-browser-panel .dsb-scan-close{cursor:pointer;color:#9aa1b5}' +
2382
+ '#dsh-skill-browser-panel .dsb-scan-list{overflow-y:auto;padding:8px 16px 16px;font-size:12px;color:#dfe3ee}' +
2383
+ '#dsh-skill-browser-panel .dsb-scan-sec{margin:12px 0 4px;font-weight:700;color:var(--dsb-brand,#8fc7ff)}' +
2384
+ '#dsh-skill-browser-panel .dsb-scan-row{padding:5px 0;border-bottom:1px dashed rgba(255,255,255,.08)}' +
2385
+ '#dsh-skill-browser-panel .dsb-scan-dim{color:#8b93a8;font-size:11px}' +
2386
+ '#dsh-skill-browser-panel .dsb-scan-desc{color:#9aa1b5;font-size:11px;margin-top:2px}' +
2387
+ '#dsh-skill-browser-panel .dsb-scan-on{color:#5fd08a;font-size:11px}' +
2388
+ '#dsh-skill-browser-panel .dsb-scan-off{color:#c9a24a;font-size:11px}' +
2389
+ '#dsh-skill-browser-panel .dsb-ai-prog{position:absolute;left:50%;bottom:16px;transform:translateX(-50%);z-index:40;width:min(340px,82%);box-sizing:border-box;background:#1f2430;border:1px solid rgba(255,255,255,.20);border-radius:10px;padding:10px 14px;box-shadow:0 10px 28px rgba(0,0,0,.55);font-size:11.5px;color:#e8ecf6}' +
2390
+ '#dsh-skill-browser-panel .dsb-ai-prog-txt{font-weight:600}' +
2391
+ '#dsh-skill-browser-panel .dsb-ai-prog-bar{height:6px;border-radius:3px;background:rgba(255,255,255,.14);margin-top:8px;overflow:hidden}' +
2392
+ '#dsh-skill-browser-panel .dsb-ai-prog-bar>i{display:block;height:100%;width:0;background:linear-gradient(90deg,var(--dsb-onbrand-bg1,#5b6cff),var(--dsb-onbrand-bg2,#2fa8ff));transition:width .2s}' +
1387
2393
  '#dsh-skill-browser-panel .dsb-plan-list{max-height:52vh;overflow-y:auto;padding:2px 4px 6px;display:flex;' +
1388
2394
  'flex-direction:column;gap:5px}' +
1389
2395
  '#dsh-skill-browser-panel .dsb-plan-dom{background:var(--dsb-soft,rgba(255,255,255,.04));' +
@@ -1401,7 +2407,7 @@ window.__ModuleLoader__.load({
1401
2407
  '#dsh-skill-browser-panel .dsb-movebtn{background:var(--chipBg,rgba(255,255,255,.06));border:1px solid var(--pbd,rgba(255,255,255,.14));color:var(--dsb-dim,#b9c2d8);' +
1402
2408
  'border-radius:6px;font-size:10.5px;padding:2px 7px;cursor:pointer;white-space:nowrap}' +
1403
2409
  '#dsh-skill-browser-panel .dsb-movebtn:hover{background:var(--dsb-hover,rgba(120,140,255,.24));color:var(--ptx,#fff);border-color:var(--stA,#7c8dff)}' +
1404
- '#dsh-skill-browser-panel .dsb-card-foot{display:flex;align-items:center;justify-content:space-between;gap:6px;margin-top:6px}' +
2410
+ '#dsh-skill-browser-panel .dsb-card-foot{display:flex;align-items:center;justify-content:space-between;gap:6px;margin-top:auto;padding-top:8px}' +
1405
2411
  '#dsh-skill-browser-panel .dsb-card-gtag{font-size:10px;color:var(--dsb-brand,#8fc7ff);opacity:.95;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}' +
1406
2412
  /* ── v1.9.1:调用台账看板(全部改用语义变量,浅色皮肤同样可读)── */
1407
2413
  '#dsh-skill-browser-panel .dsb-lbar{display:flex;flex-wrap:wrap;align-items:center;gap:6px;padding:0 0 8px}' +
@@ -1471,23 +2477,26 @@ window.__ModuleLoader__.load({
1471
2477
  panel.id = PANEL_ID;
1472
2478
  panel.innerHTML =
1473
2479
  '<div class="dsb-head">' +
1474
- '<div class="dsb-title">🧩 技能浏览器</div>' +
2480
+ '<div class="dsb-title">技能与 MCP 浏览器</div>' +
1475
2481
  '<div class="dsb-tabs">' +
1476
2482
  '<button id="dsb-tab-skills" class="dsb-tab" data-view="skills">技能浏览</button>' +
1477
- '<button id="dsb-tab-ledger" class="dsb-tab" data-view="ledger">📊 调用台账</button>' +
2483
+ '<button id="dsb-tab-ledger" class="dsb-tab" data-view="ledger">调用台账</button>' +
2484
+ '<button id="dsb-tab-mcp" class="dsb-tab" data-view="mcp">MCP</button>' +
2485
+ '<button id="dsb-tab-guide" class="dsb-tab" data-view="guide">快速上手</button>' +
1478
2486
  '</div>' +
1479
2487
  '<div class="dsb-headbtns">' +
1480
- '<button id="dsb-skin-btn" class="dsb-btn" title="切换皮肤(三球同步 · 128款)">🎨</button>' +
1481
- '<button id="dsb-setting" class="dsb-btn" title="开关设置">⚙</button>' +
1482
- '<button id="dsb-hot" class="dsb-btn" title="' + esc(T("hotReloadTip")) + '">⚡</button>' +
1483
- '<button id="dsb-refresh" class="dsb-btn" title="重新扫描技能目录">↻ 刷新</button>' +
1484
- '<button id="dsb-close" class="dsb-btn" title="关闭">×</button>' +
2488
+ '<button id="dsb-skin-btn" class="dsb-btn" title="切换皮肤(三球同步 · 130款)">' + DSB_ICONS.palette + '</button>' +
2489
+ '<button id="dsb-setting" class="dsb-btn" title="开关设置">' + DSB_ICONS.gear + '</button>' +
2490
+ '<button id="dsb-hot" class="dsb-btn" title="' + esc(T("hotReloadTip")) + '">' + DSB_ICONS.bolt + '</button>' +
2491
+ '<button id="dsb-refresh" class="dsb-btn" title="刷新">' + DSB_ICONS.refresh + '</button>' +
2492
+ '<button id="dsb-close" class="dsb-btn" title="关闭">' + DSB_ICONS.close + '</button>' +
2493
+ '<a class="dsb-btn dsb-star-link" href="https://github.com/loyalchiiina/dsh-skill-browser" target="_blank" rel="noopener" title="去 GitHub 点个 Star ⭐">' + DSB_ICONS.star + '</a>' +
1485
2494
  '</div>' +
1486
2495
  '</div>' +
1487
2496
  '<div id="dsb-skin" class="dsb-skinmenu"></div>' +
1488
2497
  '<div id="dsb-subtitle" class="dsb-subtitle"></div>' +
1489
2498
  '<div id="dsb-settings" class="dsb-settings" style="display:none">' +
1490
- '<label class="dsb-set-row"><input type="checkbox" id="dsb-set-ball" /> 技能浏览器悬浮球(🧩)</label>' +
2499
+ '<label class="dsb-set-row"><input type="checkbox" id="dsb-set-ball" /> 技能与 MCP 浏览器悬浮球(🧩)</label>' +
1491
2500
  '<label class="dsb-set-row"><input type="checkbox" id="dsb-set-font" /> 字体插件悬浮球(🔤)</label>' +
1492
2501
  '<div class="dsb-set-row" style="flex-wrap:wrap">' +
1493
2502
  '<span style="min-width:70px">技能库位置</span>' +
@@ -1495,12 +2504,11 @@ window.__ModuleLoader__.load({
1495
2504
  '<button id="dsb-set-root-browse" class="dsb-btn" title="打开系统文件夹选择框">📁 选择</button>' +
1496
2505
  '<button id="dsb-set-root-save" class="dsb-btn">保存</button>' +
1497
2506
  '</div>' +
1498
- '<div class="dsb-set-row" style="flex-wrap:wrap">' +
1499
- '<span style="min-width:70px">悬浮球皮肤</span>' +
1500
- '<select id="dsb-set-skin" style="flex:1;min-width:220px;background:rgba(0,0,0,.35);border:1px solid rgba(255,255,255,.14);border-radius:6px;color:inherit;font-size:12px;padding:4px 6px;outline:none"></select>' +
1501
- '<span class="dsb-set-hint">三球同步 · 128 款</span>' +
1502
- '</div>' +
1503
2507
  '<label class="dsb-set-row"><input type="checkbox" id="dsb-set-ledger" /> 失效台账自动登记(记录每次技能工具调用结果)</label>' +
2508
+ '<div class="dsb-set-row"><span style="min-width:70px">AI 分组缓存</span>' +
2509
+ '<button id="dsb-set-clear-cache" class="dsb-btn" title="清除 AI 分组的本地结果缓存(只动缓存文件,不碰任何技能文件)">🗑 清除缓存</button>' +
2510
+ '<span id="dsb-set-cache-status" class="dsb-set-hint">命中缓存时 AI 分组秒回</span>' +
2511
+ '</div>' +
1504
2512
  '<span id="dsb-set-status" class="dsb-set-hint">当前技能库:识别中…</span>' +
1505
2513
  '<span class="dsb-set-hint">💡 悬浮球显隐也可在 <b>DSH 设置页 → 「悬浮球导航」分区</b> 中控制;悬浮球可拖动;清单面板始终居中</span>' +
1506
2514
  // ── v1.7.4 广告区(用户指定落位:设置面板 hint 之后)──
@@ -1530,7 +2538,21 @@ window.__ModuleLoader__.load({
1530
2538
  '<div id="dsb-chips" class="dsb-chips"></div>' +
1531
2539
  '<div id="dsb-subs" class="dsb-subs"></div>' +
1532
2540
  '<div id="dsb-cards" class="dsb-cards"></div>' +
1533
- '<div id="dsb-ledger" class="dsb-ledger" style="display:none">' +
2541
+ '<div id="dsb-mcp" class="dsb-mcp" style="display:none"></div>' +
2542
+ '<div id="dsb-guide" class="dsb-guide" style="display:none">' +
2543
+ '<div class="dsb-guide-inner">' +
2544
+ '<h3>' + DSB_ICONS.book + '快速上手</h3>' +
2545
+ '<p class="dsb-guide-sub">60 秒学会用技能与 MCP 浏览器管理你的技能库</p>' +
2546
+ '<div class="dsb-guide-card">' + dsbGico('folder') + '<b>① 先指定技能库位置(第一步)</b><br>点右上角 <b>设置</b> 齿轮 → 找到「<b>技能库位置</b>」→ 点 <b>选择</b> 弹出系统文件夹框,选中你的技能包文件夹(例如 <b>E:\DSHskills</b>)→ 点 <b>保存</b>。输入框留空 = 自动识别(~/.dsh/skills 等默认位置)。<br>保存后会自动重扫,下方状态行显示「当前技能库:…」确认路径正确。</div>' +
2547
+ '<div class="dsb-guide-card">' + dsbGico('refresh') + '<b>② 刷新,让新技能进列表</b><br>技能文件增删改之后,点右上角 <b>刷新</b>(↻)重新扫描技能库。要让它<b>不重启 DSH 立即生效</b>,再点一下 <b>热重载</b>(⚡)。顺序永远是:<b>先选库 → 再刷新 → 最后热重载</b>。</div>' +
2548
+ '<div class="dsb-guide-card">' + dsbGico('search') + '<b>③ 搜索与筛选技能</b><br>顶部搜索框支持<b>技能名 / 中文简介 / 描述</b>关键字。下方分组栏:<b>我的分组</b>(树状/平铺切换)→ <b>关键词分组</b>(按触发词自动归类)→ <b>AI 自动分组</b>(调模型智能归类)。存在自定义分组时,默认分类行会自动隐藏。</div>' +
2549
+ '<div class="dsb-guide-card">' + dsbGico('eye') + '<b>④ 看技能详情 / 启停技能</b><br>点任意技能卡片展开详情:名称、简介、触发词、SKILL.md 全文、路径、最近调用记录。详情里可直接<b>禁用/启用</b>该技能(停用后模型技能目录里即摘除)。</div>' +
2550
+ '<div class="dsb-guide-card">' + dsbGico('chart') + '<b>⑤ 调用台账</b><br>切到「调用台账」:选起止日期 + 粒度(日/周/月/年)→ 看调用次数排行、失败率与趋势图。排查「技能怎么没生效」先来这里。需在设置里勾选「失效台账自动登记」。</div>' +
2551
+ '<div class="dsb-guide-card">' + dsbGico('folders') + '<b>⑥ 分组管理</b><br>「+新建分组」可建大类/子类;技能卡片菜单里可「移动到分组」。删除分组不会删技能文件,只是视图归类。</div>' +
2552
+ '<div class="dsb-guide-card">' + dsbGico('grid') + '<b>⑦ 认识面板其余按钮</b><br>右上角从左到右:<b>皮肤</b>(三球同步换肤,130 款)、<b>设置</b>(本页上面那些开关)、<b>热重载</b>(⚡ 不重启生效)、<b>刷新</b>(↻ 重扫技能库)、<b>关闭</b>、<b>GitHub Star</b>。</div>' +
2553
+ '<p class="dsb-guide-tip">小技巧:改了技能文件先点刷新再搜;AI 分组前先在设置里确认模型探针通过。</p>' +
2554
+ '</div></div>' +
2555
+ '<div id="dsb-ledger" class="dsb-ledger" style="display:none">' +
1534
2556
  '<div id="dsb-lbar" class="dsb-lbar"></div>' +
1535
2557
  '<div id="dsb-lcards" class="dsb-lcards"></div>' +
1536
2558
  '<div id="dsb-ltrend" class="dsb-ltrend"></div>' +
@@ -1598,6 +2620,10 @@ window.__ModuleLoader__.load({
1598
2620
  var pact = pa.getAttribute("data-plan-act");
1599
2621
  if (pact === "replace") applyPlanFlow("replace");
1600
2622
  else if (pact === "merge") applyPlanFlow("merge");
2623
+ else if (pact === "recache") {
2624
+ closePlanMenu();
2625
+ postGroups({ action: "clear-classify-cache" }, function () { aiClassifyFlow(); });
2626
+ }
1601
2627
  else closePlanMenu();
1602
2628
  return;
1603
2629
  }
@@ -1639,6 +2665,12 @@ window.__ModuleLoader__.load({
1639
2665
  paintGroups();
1640
2666
  return;
1641
2667
  }
2668
+ var gf = ev.target.closest("[data-gfold]");
2669
+ if (gf) {
2670
+ S.gFold = gf.getAttribute("data-gfold") === "1";
2671
+ paintGroups();
2672
+ return;
2673
+ }
1642
2674
  var add = ev.target.closest("[data-gadd]");
1643
2675
  if (add) {
1644
2676
  var how = add.getAttribute("data-gadd");
@@ -1651,19 +2683,23 @@ window.__ModuleLoader__.load({
1651
2683
  var gs = ev.target.closest("[data-gsub]");
1652
2684
  if (gs) {
1653
2685
  S.gsub = gs.getAttribute("data-gsub") || "";
1654
- paintGroups(); paintCards();
2686
+ paintGroups(); paintCards(); paintSortbar();
1655
2687
  return;
1656
2688
  }
1657
2689
  var card = ev.target.closest("[data-gsel]");
1658
2690
  if (card) {
1659
2691
  S.group = card.getAttribute("data-gsel") || "all";
1660
2692
  S.gsub = "";
1661
- paintGroups(); paintCards();
2693
+ paintGroups(); paintCards(); paintSortbar();
1662
2694
  }
1663
2695
  });
1664
2696
 
1665
2697
  $("dsb-close").addEventListener("click", function () { closePanel(); });
1666
- $("dsb-refresh").addEventListener("click", function () { if (S.view === "ledger") loadLedgerRefresh(); else load(true); });
2698
+ $("dsb-refresh").addEventListener("click", function () {
2699
+ if (S.view === "ledger") loadLedgerRefresh();
2700
+ else if (S.view === "mcp") loadMcp(true);
2701
+ else load(true);
2702
+ });
1667
2703
 
1668
2704
  // ── v1.9.4 热加载:重新加载插件代码,不重启桌面端 ──────────────
1669
2705
  $("dsb-hot").addEventListener("click", function () { hotReloadFlow(); });
@@ -1695,7 +2731,23 @@ window.__ModuleLoader__.load({
1695
2731
  });
1696
2732
  var rootBrowse = $("dsb-set-root-browse");
1697
2733
  if (rootBrowse) rootBrowse.addEventListener("click", function () { browseForDirectory(); });
1698
- $("dsb-q").addEventListener("input", function () { S.q = this.value; S.sub = ""; paintChips(); paintSubs(); paintCards(); });
2734
+ // v2.0.14 清除 AI 分组缓存按钮(只清缓存文件,不碰任何技能文件)
2735
+ var clearCacheBtn = $("dsb-set-clear-cache");
2736
+ if (clearCacheBtn) clearCacheBtn.addEventListener("click", function () {
2737
+ clearCacheBtn.disabled = true;
2738
+ postGroups({ action: "clear-classify-cache" }, function (d) {
2739
+ clearCacheBtn.disabled = false;
2740
+ var st = $("dsb-set-cache-status");
2741
+ if (d && d.ok) {
2742
+ if (st) st.textContent = "✅ 已清除,下次 AI 分组将重新调用模型";
2743
+ showToast("ok", "AI 分组缓存已清除");
2744
+ } else {
2745
+ if (st) st.textContent = "清除失败:" + String((d && d.error) || "unknown");
2746
+ showToast("err", "清除缓存失败");
2747
+ }
2748
+ });
2749
+ });
2750
+ $("dsb-q").addEventListener("input", function () { S.q = this.value; S.sub = ""; paintChips(); paintSubs(); paintCards(); paintSortbar(); });
1699
2751
  // v1.9.6 技能列表滚到底自动追加一批(无感分页,避免一次性渲染上千节点)
1700
2752
  $("dsb-cards").addEventListener("scroll", function () {
1701
2753
  var b = this;
@@ -1713,6 +2765,19 @@ window.__ModuleLoader__.load({
1713
2765
  if (!S.ledger) loadLedger(); else paint();
1714
2766
  if (!S.lstat) loadStats();
1715
2767
  });
2768
+ $("dsb-tab-guide").addEventListener("click", function () { S.view = "guide"; S.detail = null; S.ledgerDetail = null; paint(); });
2769
+ $("dsb-tab-mcp").addEventListener("click", function () {
2770
+ S.view = "mcp"; S.detail = null; S.ledgerDetail = null;
2771
+ paint();
2772
+ if (!S.mcp || !S.mcp.data) loadMcp(true);
2773
+ else if (!S.mcp.stats) loadMcpStats();
2774
+ });
2775
+ // v2.0.0 MCP 面板:事件委托(innerHTML 重绘不丢监听)
2776
+ var mcpHost = $("dsb-mcp");
2777
+ if (mcpHost) {
2778
+ mcpHost.addEventListener("click", handleMcpClick);
2779
+ mcpHost.addEventListener("change", handleMcpChange);
2780
+ }
1716
2781
  $("dsb-ledger-table").addEventListener("click", function (ev) {
1717
2782
  // 排行表头排序
1718
2783
  var sEl = ev.target.closest("[data-lsort]");
@@ -1757,7 +2822,7 @@ window.__ModuleLoader__.load({
1757
2822
  var el = ev.target.closest("[data-sub]");
1758
2823
  if (!el) return;
1759
2824
  S.sub = el.getAttribute("data-sub");
1760
- paintSubs(); paintCards();
2825
+ paintSubs(); paintCards(); paintSortbar();
1761
2826
  });
1762
2827
  // v1.7.0 排序切换 + v1.7.1 四态循环 + 分类级批量启停(事件委托到排序栏容器)
1763
2828
  $("dsb-sortbar").addEventListener("click", function (ev) {
@@ -1775,6 +2840,13 @@ window.__ModuleLoader__.load({
1775
2840
  openBatchMenu(agg, agg.getAttribute("data-bagg") || "global");
1776
2841
  return;
1777
2842
  }
2843
+ var cc = ev.target.closest("[data-cardcols]");
2844
+ if (cc) {
2845
+ S.cardCols = parseInt(cc.getAttribute("data-cardcols"), 10) || 0;
2846
+ try { localStorage.setItem("dsb-card-cols", String(S.cardCols)); } catch (e2) { }
2847
+ paintSortbar(); paintCards();
2848
+ return;
2849
+ }
1778
2850
  });
1779
2851
  // v1.7.4 设置面板推广卡(data-ext-url)点击直达 GitHub:
1780
2852
  // 广告卡迁入 #dsb-settings 后,原 #dsb-cards 委托覆盖不到,此处补一条同款委托。
@@ -1834,10 +2906,30 @@ window.__ModuleLoader__.load({
1834
2906
  S.panelBuilt = false;
1835
2907
  }
1836
2908
 
2909
+ function positionPanelNearBall() {
2910
+ // m04874: 面板跟随悬浮球(与字体插件一致):水平居中于球并夹在视口内;
2911
+ // 球在屏幕下半部 -> 面板放球上方,否则放球下方。
2912
+ var panel = $(PANEL_ID), ball = $(BALL_ID);
2913
+ if (!panel || !ball) return;
2914
+ var r = ball.getBoundingClientRect();
2915
+ var pw = panel.offsetWidth || 940, ph = panel.offsetHeight || 660;
2916
+ var left = r.left + r.width / 2 - pw / 2;
2917
+ left = Math.max(8, Math.min(window.innerWidth - pw - 8, left));
2918
+ var top = r.top - ph - 10;
2919
+ if (top < 8) top = r.bottom + 10;
2920
+ top = Math.max(8, Math.min(window.innerHeight - ph - 8, top));
2921
+ panel.style.left = left + "px";
2922
+ panel.style.top = top + "px";
2923
+ panel.style.right = "auto";
2924
+ panel.style.bottom = "auto";
2925
+ panel.style.transform = "none";
2926
+ }
2927
+
1837
2928
  function openPanel() {
1838
2929
  buildPanel();
1839
2930
  S.open = true;
1840
2931
  paint();
2932
+ positionPanelNearBall();
1841
2933
  if (!S.data && !S.loading) load(false);
1842
2934
  }
1843
2935
 
@@ -1847,20 +2939,28 @@ window.__ModuleLoader__.load({
1847
2939
  panel.style.display = S.open ? "flex" : "none";
1848
2940
  if (!S.open) return;
1849
2941
  var isLedger = S.view === "ledger";
1850
- var q = $("dsb-q"), chips = $("dsb-chips"), subs = $("dsb-subs"), cards = $("dsb-cards"), lg = $("dsb-ledger"), sortbar = $("dsb-sortbar"), groups = $("dsb-groups");
1851
- if (q) q.style.display = isLedger ? "none" : "";
1852
- if (groups) groups.style.display = isLedger ? "none" : "";
1853
- if (chips) chips.style.display = isLedger ? "none" : "";
1854
- if (subs) subs.style.display = isLedger ? "none" : "";
1855
- if (cards) cards.style.display = isLedger ? "none" : "";
1856
- if (sortbar) sortbar.style.display = isLedger ? "none" : "";
2942
+ var isGuide = S.view === "guide";
2943
+ var isMcp = S.view === "mcp";
2944
+ var q = $("dsb-q"), chips = $("dsb-chips"), subs = $("dsb-subs"), cards = $("dsb-cards"), lg = $("dsb-ledger"), sortbar = $("dsb-sortbar"), groups = $("dsb-groups"), gd = $("dsb-guide"), mcpEl = $("dsb-mcp");
2945
+ var skillsVis = !isLedger && !isGuide && !isMcp ? "" : "none";
2946
+ if (q) q.style.display = skillsVis;
2947
+ if (groups) groups.style.display = skillsVis;
2948
+ if (chips) chips.style.display = skillsVis;
2949
+ if (subs) subs.style.display = skillsVis;
2950
+ if (cards) cards.style.display = skillsVis;
2951
+ if (sortbar) sortbar.style.display = skillsVis;
1857
2952
  if (lg) lg.style.display = isLedger ? "flex" : "none";
1858
- var t1 = $("dsb-tab-skills"), t2 = $("dsb-tab-ledger");
1859
- if (t1) t1.classList.toggle("dsb-tab-on", !isLedger);
2953
+ if (gd) gd.style.display = isGuide ? "block" : "none";
2954
+ if (mcpEl) mcpEl.style.display = isMcp ? "flex" : "none";
2955
+ var t1 = $("dsb-tab-skills"), t2 = $("dsb-tab-ledger"), t3 = $("dsb-tab-guide"), tm = $("dsb-tab-mcp");
2956
+ if (t1) t1.classList.toggle("dsb-tab-on", S.view === "skills");
1860
2957
  if (t2) t2.classList.toggle("dsb-tab-on", isLedger);
2958
+ if (t3) t3.classList.toggle("dsb-tab-on", isGuide);
2959
+ if (tm) tm.classList.toggle("dsb-tab-on", isMcp);
1861
2960
  paintHeader();
1862
2961
  if (isLedger) paintLedger();
1863
- else { paintGroups(); paintSortbar(); paintChips(); paintSubs(); paintCards(); }
2962
+ else if (isMcp) paintMcp();
2963
+ else if (!isGuide) { paintGroups(); paintSortbar(); paintChips(); paintSubs(); paintCards(); }
1864
2964
  paintDetail();
1865
2965
  }
1866
2966
 
@@ -1885,14 +2985,19 @@ window.__ModuleLoader__.load({
1885
2985
  var bar = '<div class="dsb-gbar">';
1886
2986
  bar += '<span class="dsb-gtitle" title="' + esc(T("groupsHint")) + '">' + esc(T("groupsTitle")) + "</span>";
1887
2987
  bar += '<button class="dsb-gadd' + (S.group === "all" ? " dsb-gadd-on" : "") + '" data-gsel="all">' +
1888
- esc(T("groupAll")) + ' <span class="dsb-gn">' + total + "</span></button>";
2988
+ esc(T("groupAll")) + ' <span class="dsb-gn">' + total + " 项</span></button>";
1889
2989
  bar += '<button class="dsb-gadd' + (S.group === "ungrouped" ? " dsb-gadd-on" : "") + '" data-gsel="ungrouped">' +
1890
- esc(T("groupUngrouped")) + ' <span class="dsb-gn">' + ungrouped + "</span></button>";
1891
- bar += '<span class="dsb-gsp"></span>';
2990
+ esc(T("groupUngrouped")) + ' <span class="dsb-gn">' + ungrouped + " 项</span></button>";
2991
+ bar += '<span class="dsb-gsep"></span>';
1892
2992
  bar += '<button class="dsb-gadd' + (treeMode ? " dsb-gadd-on" : "") + '" data-gmode="tree" title="' +
1893
2993
  esc(T("groupModeTreeTip")) + '">' + esc(T("groupModeTree")) + "</button>";
1894
2994
  bar += '<button class="dsb-gadd' + (treeMode ? "" : " dsb-gadd-on") + '" data-gmode="flat" title="' +
1895
2995
  esc(T("groupModeFlatTip")) + '">' + esc(T("groupModeFlat")) + "</button>";
2996
+ bar += '<span class="dsb-gsep"></span>';
2997
+ if (treeMode) {
2998
+ bar += '<button class="dsb-gadd' + (!S.gFold ? " dsb-gadd-on" : "") + '" data-gfold="0" title="展开所有分组的子类">全部展开</button>';
2999
+ bar += '<button class="dsb-gadd' + (S.gFold ? " dsb-gadd-on" : "") + '" data-gfold="1" title="收起所有分组的子类">全部收起</button>';
3000
+ }
1896
3001
  bar += '<button class="dsb-gadd dsb-gadd-kw' + (S.groupBusy === "kw-classify" ? " dsb-gbusy" : "") +
1897
3002
  '" data-gadd="kw" title="' + esc(T("groupKwTip")) + '">' +
1898
3003
  esc(S.groupBusy === "kw-classify" ? T("processingText") : T("groupKw")) + "</button>";
@@ -1908,16 +3013,22 @@ window.__ModuleLoader__.load({
1908
3013
  miCls = " dsb-gmodel-idle";
1909
3014
  miTip = T("modelProbeTip");
1910
3015
  } else if (mi.ok) {
1911
- miText = "🤖 " + esc(TF("modelUsing", { model: mi.model })) +
1912
- (mi.source && mi.source !== "config" ? ' <span class="dsb-gn">' + esc(T(modelSourceKey(mi.source))) + "</span>" : "");
3016
+ // v2.0.9 用户要求:只显示「跟随当前对话模型」,具体模型放 title 悬浮可见。
3017
+ miText = "🤖 跟随当前对话模型 · 正常";
1913
3018
  miCls = " dsb-gmodel-ok";
1914
- miTip = (mi.provider || "") + " / " + (mi.model || "") + "(" + (mi.source || "") + ")";
3019
+ miTip = "跟随当前对话模型 · 实际:" + (mi.provider || "") + " / " + (mi.model || "") + "(" + (mi.source || "") + ")";
1915
3020
  } else {
1916
3021
  miText = "⚠ " + esc(T("modelNone"));
1917
3022
  miCls = " dsb-gmodel-bad";
1918
3023
  miTip = mi.error || T("modelNone");
1919
3024
  }
1920
3025
  bar += '<span class="dsb-gsp"></span>';
3026
+ if (S.aiLastMs > 0) {
3027
+ var lastTxt = S.aiLastCached
3028
+ ? "⚡ 上次分组:缓存 0.0s"
3029
+ : ("⏱ 上次分组:" + (S.aiLastMs / 1000).toFixed(1) + "s" + (S.aiLastBatches > 1 ? (" · 并行" + S.aiLastBatches + "批") : " · 串行"));
3030
+ bar += '<span class="dsb-ai-last" title="最近一次分组的耗时(折叠 / 切走 / 重开面板都保留)">' + esc(lastTxt) + "</span>";
3031
+ }
1921
3032
  bar += '<span class="dsb-gmodel' + miCls + '" data-gmodel="1" title="' + esc(miTip) + '">' + miText + "</span>";
1922
3033
  bar += "</div>";
1923
3034
 
@@ -1948,9 +3059,8 @@ window.__ModuleLoader__.load({
1948
3059
  "</div>";
1949
3060
  // 子类行常驻:选中该大类时把「整个大类」作为第一个可选项。
1950
3061
  // 用 ├─ / └─ 连接符做成真正的树枝,每行一个子类,层层展开。
1951
- if (kids.length || on) {
3062
+ if (kids.length && !S.gFold) {
1952
3063
  var rows = [];
1953
- if (on) rows.push({ id: "", label: T("groupWhole"), emoji: "", n: n, tip: T("groupWhole") });
1954
3064
  for (var k = 0; k < kids.length; k++) {
1955
3065
  var sg = kids[k];
1956
3066
  rows.push({
@@ -1971,7 +3081,7 @@ window.__ModuleLoader__.load({
1971
3081
  '<button class="dsb-gsub' + (sOn ? " dsb-gsub-on" : "") +
1972
3082
  '" data-gsub="' + esc(it.id) + '" title="' + esc(it.tip) + '">' +
1973
3083
  (it.emoji ? esc(it.emoji) + " " : "") + esc(it.label) +
1974
- ' <span class="dsb-gn">' + it.n + "</span>" +
3084
+ ' <span class="dsb-gn">' + it.n + " 项</span>" +
1975
3085
  "</button>" +
1976
3086
  "</div>";
1977
3087
  }
@@ -2015,14 +3125,12 @@ window.__ModuleLoader__.load({
2015
3125
  var kids2 = subsOf(cur.id);
2016
3126
  if (kids2.length) {
2017
3127
  body += '<div class="dsb-gsubs"><span class="dsb-gtitle">└ ' + esc(T("groupSubsOf")) + "</span>";
2018
- body += '<button class="dsb-gadd' + (S.gsub ? "" : " dsb-gadd-on") + '" data-gsub="">' +
2019
- esc(T("groupWhole")) + ' <span class="dsb-gn">' + domainCount(cur) + "</span></button>";
2020
3128
  for (var q = 0; q < kids2.length; q++) {
2021
3129
  var sg2 = kids2[q];
2022
3130
  var sn2 = (sg2.members || []).length;
2023
3131
  body += '<button class="dsb-gadd' + (S.gsub === sg2.id ? " dsb-gadd-on" : "") +
2024
3132
  '" data-gsub="' + esc(sg2.id) + '" title="' + esc(sg2.name + " · " + sn2 + " 项") + '">' +
2025
- esc((sg2.emoji || "📁") + " " + sg2.name) + ' <span class="dsb-gn">' + sn2 + "</span></button>";
3133
+ esc((sg2.emoji || "📁") + " " + sg2.name) + ' <span class="dsb-gn">' + sn2 + " 项</span></button>";
2026
3134
  }
2027
3135
  body += "</div>";
2028
3136
  }
@@ -2051,6 +3159,11 @@ window.__ModuleLoader__.load({
2051
3159
  function paintChips() {
2052
3160
  var box = $("dsb-chips"); if (!box) return;
2053
3161
  if (!S.data) { box.innerHTML = ""; return; }
3162
+ // 2026-10-28 (m01120): 自定义分组(关键词/AI/手动建的大类)一旦存在,
3163
+ // 默认分类 chip 行就整体隐藏——两排并排表达同一批技能,纯冗余;
3164
+ // 用户删光所有分组后自动恢复默认分类视图。
3165
+ if (topGroups().length) { box.innerHTML = ""; box.style.display = "none"; return; }
3166
+ box.style.display = "";
2054
3167
  var cs = S.data.categories || [];
2055
3168
  var counts = {};
2056
3169
  var sk = S.data.skills || [];
@@ -2144,25 +3257,22 @@ window.__ModuleLoader__.load({
2144
3257
  // v1.7.2 全局总开关(最重要):标签"全部技能",状态=所有技能聚合
2145
3258
  // (全启用→右绿 / 全停用→左灰 / 混合→居中半开+title"部分停用");
2146
3259
  // 点击任意态弹面板内确认菜单 openBatchMenu
2147
- var sk = (S.data && S.data.skills) || [];
2148
- var ga = aggCount(sk, null);
3260
+ // v2.0.7 聚合开关作用于「当前筛选/分组显示的列表」(filtered),并显示数量
3261
+ var fl = filtered();
3262
+ var ga = aggCount(fl, null);
2149
3263
  var gs = aggState(ga);
2150
- var gTitle = gs === "half" ? TF("aggMixedTitle", { x: ga.off, y: ga.total })
2151
- : (gs === "on" ? T("aggAllOnTitle") : T("aggAllOffTitle"));
2152
- var html = btns + aggSwitchHtml(gs, 'data-bagg="global"', gTitle, T("aggAllLabel"), false);
2153
- // v1.7.2 分类级滑块:替换 v1.7.0 的"全启用/全停用"两个按钮——小一号胶囊
2154
- // 滑块 + 旁边小字"停用 x/y"(该分类已停用数/总数),聚合逻辑同全局
2155
- if (S.cat !== "all") {
2156
- var ca = aggCount(sk, S.cat);
2157
- var cs = aggState(ca);
2158
- var cTitle = catLabel(S.cat) + " · " + (cs === "half" ? TF("aggMixedTitle", { x: ca.off, y: ca.total })
2159
- : (cs === "on" ? T("aggAllOnTitle") : T("aggAllOffTitle")));
2160
- html += '<span class="dsb-sw-sep"></span>' +
2161
- aggSwitchHtml(cs, 'data-bagg="' + esc(S.cat) + '"', cTitle, "", true) +
2162
- '<span class="dsb-batch-status">' + esc(TF("aggCatStat", { x: ca.off, y: ca.total })) + '</span>';
2163
- }
3264
+ var gTitle = (gs === "half" ? TF("aggMixedTitle", { x: ga.off, y: ga.total })
3265
+ : (gs === "on" ? T("aggAllOnTitle") : T("aggAllOffTitle"))) + "(当前分组 " + ga.total + " 项)";
3266
+ var hasFilter = (S.cat !== "all") || (S.group !== "all") || !!S.sub || !!S.q.trim();
3267
+ var aggLabel = hasFilter ? ("当前 " + ga.total + " 项") : (T("aggAllLabel") + " " + ga.total + " 项");
3268
+ var html = btns + aggSwitchHtml(gs, 'data-bagg="filtered"', gTitle, aggLabel, false);
2164
3269
  // v1.7.1 保留:批量进行中显示"处理中…"(滑块同时 aria-disabled 防重入)
2165
3270
  if (S.toggling) html += '<span class="dsb-batch-status">' + esc(T("processingText")) + '</span>';
3271
+ // v2.0.6 每行列数(0=自适应)
3272
+ html += '<span class="dsb-sw-sep"></span><span class="dsb-batch-status">每行</span>';
3273
+ [0, 2, 3, 4, 5, 6].forEach(function (n) {
3274
+ html += '<button class="dsb-sortbtn' + (S.cardCols === n ? ' dsb-sortbtn-on' : '') + '" data-cardcols="' + n + '" title="' + (n ? ("每行 " + n + " 个") : "自适应") + '">' + (n || '自') + '</button>';
3275
+ });
2166
3276
  box.innerHTML = html;
2167
3277
  }
2168
3278
 
@@ -2315,16 +3425,20 @@ window.__ModuleLoader__.load({
2315
3425
  // ③paintSortbar 重算全局+分类聚合滑块与"停用 x/y"小字;④paintHeader 刷副标题。
2316
3426
  function batchToggle(scope, disable) {
2317
3427
  if (S.toggling) return;
2318
- var isGlobal = scope === "global";
2319
3428
  var sk = (S.data && S.data.skills) || [];
2320
3429
  var dirs = [];
2321
- for (var i = 0; i < sk.length; i++) {
2322
- if (isGlobal || sk[i].cat === scope) dirs.push(sk[i].dir);
3430
+ if (scope === "filtered") {
3431
+ dirs = filtered().map(function (s) { return s.dir; });
3432
+ } else {
3433
+ var isGlobal = scope === "global";
3434
+ for (var i = 0; i < sk.length; i++) {
3435
+ if (isGlobal || sk[i].cat === scope) dirs.push(sk[i].dir);
3436
+ }
2323
3437
  }
2324
3438
  if (!dirs.length) return;
2325
3439
  S.toggling = true;
2326
3440
  paintSortbar(); // 聚合滑块进入"处理中…"半透明态(aria-disabled 防重入)
2327
- var body = isGlobal ? { dirs: dirs, disable: disable } : { category: scope, disable: disable };
3441
+ var body = { dirs: dirs, disable: disable };
2328
3442
  fetch(apiBase() + "/toggle-batch", {
2329
3443
  method: "POST",
2330
3444
  headers: { "content-type": "application/json" },
@@ -2353,7 +3467,7 @@ window.__ModuleLoader__.load({
2353
3467
  } else {
2354
3468
  showToast("ok", TF(disable ? "toastBatchDisabled" : "toastBatchEnabled", { n: okN }));
2355
3469
  }
2356
- ctxLogger("batch " + (isGlobal ? "global" : scope) + ": ok=" + okN + " fail=" + fails);
3470
+ ctxLogger("batch " + (scope === "global" ? "global" : scope) + ": ok=" + okN + " fail=" + fails);
2357
3471
  } else {
2358
3472
  showToast("err", TF("toastToggleFail", { err: (d && d.error) || "unknown" }));
2359
3473
  try { console.warn("[dsh-skill-browser] batch failed: " + ((d && d.error) || "unknown")); } catch (e) {}
@@ -2375,14 +3489,15 @@ window.__ModuleLoader__.load({
2375
3489
  if (S.toggling) return;
2376
3490
  var menu = $("dsb-bmenu"), mask = $("dsb-bmenu-mask"), panel = $(PANEL_ID);
2377
3491
  if (!menu || !mask || !panel || !anchor) return;
3492
+ var isFiltered = scope === "filtered";
2378
3493
  var isGlobal = scope === "global";
2379
- var a = aggCount((S.data && S.data.skills) || [], isGlobal ? null : scope);
3494
+ var a = isFiltered ? aggCount(filtered(), null) : aggCount((S.data && S.data.skills) || [], isGlobal ? null : scope);
2380
3495
  var n = a.total;
2381
- var scopeName = isGlobal ? T("aggAllLabel") : catLabel(scope);
2382
- var enSub = isGlobal ? "" : T("batchConfirmEnable");
2383
- var disSub = isGlobal ? TF("aggDangerText", { n: n }) : T("batchConfirmDisable");
3496
+ var scopeName = isFiltered ? "当前分组" : (isGlobal ? T("aggAllLabel") : catLabel(scope));
3497
+ var enSub = (isFiltered || isGlobal) ? "" : T("batchConfirmEnable");
3498
+ var disSub = (isFiltered || isGlobal) ? TF("aggDangerText", { n: n }) : T("batchConfirmDisable");
2384
3499
  menu.innerHTML =
2385
- '<div class="dsb-bmenu-title">' + esc(isGlobal ? T("aggMenuTitleGlobal") : T("aggMenuTitleCat")) + '<br/>' +
3500
+ '<div class="dsb-bmenu-title">' + esc((isFiltered || isGlobal) ? T("aggMenuTitleGlobal") : T("aggMenuTitleCat")) + '<br/>' +
2386
3501
  esc(TF("aggMenuStat", { scope: scopeName, n: n, x: a.off })) + '</div>' +
2387
3502
  '<button class="dsb-bmenu-item" data-bmenu-act="enable"><span>' + esc(TF("aggEnableN", { n: n })) + '</span>' +
2388
3503
  (enSub ? '<span class="dsb-bmenu-sub">' + esc(enSub) + '</span>' : "") + '</button>' +
@@ -2451,6 +3566,7 @@ window.__ModuleLoader__.load({
2451
3566
  }
2452
3567
  function paintCards() {
2453
3568
  var box = $("dsb-cards"); if (!box) return;
3569
+ box.style.gridTemplateColumns = S.cardCols ? ("repeat(" + S.cardCols + ",minmax(0,1fr))") : "";
2454
3570
  if (S.error && !S.data) {
2455
3571
  box.innerHTML = '<div class="dsb-empty">' + esc(S.error) + '<br/><br/><button class="dsb-btn" id="dsb-retry">重试</button></div>';
2456
3572
  var r = $("dsb-retry");
@@ -2494,7 +3610,7 @@ window.__ModuleLoader__.load({
2494
3610
  '<div class="dsb-card-foot">' +
2495
3611
  gTag +
2496
3612
  '<button class="dsb-movebtn" data-move="' + esc(s.dir) + '" title="' + esc(T("groupAssignTip")) + '">' +
2497
- "📁 " + esc(T("groupMove")) + "</button>" +
3613
+ "🔀 " + esc(T("groupMove")) + "</button>" +
2498
3614
  "</div>" +
2499
3615
  '<div style="margin-top:6px">' +
2500
3616
  '<label class="dsb-switch' + (off ? "" : " dsb-sw-on") + '" data-tgl="' + esc(s.dir) + '" data-to="' + (off ? "0" : "1") +
@@ -2666,43 +3782,16 @@ window.__ModuleLoader__.load({
2666
3782
  }
2667
3783
  rank.innerHTML = rh;
2668
3784
 
2669
- // ---- 5) 原始流水(人工台账 + 自动登记,折叠)----
3785
+ // ---- 5) 原始流水(只保留自动登记;人工台账区块按用户要求移除)----
2670
3786
  var l = S.ledger || {};
2671
- var rows = (l.entries || []).filter(function (e) { return e.fails > 0; });
2672
3787
  var auto = l.autoRecent || [];
2673
3788
  var flowOpen = !!S.lstatShowFlow;
2674
- var fh = '<div class="dsb-lsec"><span>🗂 ' + esc(T("lstatManual")) + "</span>" +
2675
- '<span class="sub">技能生效-失效台账.md(人工维护 + 历史登记)</span>' +
3789
+ var fh = '<div class="dsb-lsec"><span>🗂 ' + esc(T("lstatAuto")) + "</span>" +
3790
+ '<span class="sub">' + esc(T("lstatAutoTip")) + " · " + auto.length + " 条</span>" +
2676
3791
  '<button class="dsb-ldetail-toggle" data-lflow="1" style="margin-left:auto">' +
2677
3792
  esc(flowOpen ? T("lstatExpand") : T("lstatCollapse")) + "</button></div>";
2678
3793
 
2679
3794
  if (flowOpen) {
2680
- fh += '<div class="dsb-ltable">' +
2681
- '<div class="dsb-lrow dsb-lhead"><span class="dsb-lc-rank">#</span><span class="dsb-lc-skill">技能名</span>' +
2682
- '<span class="dsb-lc-fail">失败</span><span class="dsb-lc-rate">等级</span>' +
2683
- '<span class="dsb-lc-date">最近失败</span><span class="dsb-lc-status">强化状态</span></div>';
2684
- if (!rows.length) {
2685
- fh += '<div class="dsb-lrow"><span class="dsb-lc-skill" style="opacity:.7;line-height:1.8">' +
2686
- "暂无人工登记。<br/>💡 技能触发失败时,在对话里说「<b>触发失败,登记</b>」或「<b>登记失败:技能名 原因</b>」,AI 会自动写入台账。</span></div>";
2687
- }
2688
- for (var i = 0; i < rows.length; i++) {
2689
- var e = rows[i];
2690
- var lv = ledgerLevel(e.fails);
2691
- var status = e.status || "";
2692
- var statusCls = /待强化|⚠/.test(status) ? "dsb-lst-warn" : /已强化/.test(status) ? "dsb-lst-done" : "dsb-lst-ok";
2693
- fh += '<div class="dsb-lrow" data-lskill="' + esc(e.skill) + '" title="点击查看失败明细">' +
2694
- '<span class="dsb-lc-rank">' + esc(e.rank || "—") + "</span>" +
2695
- '<span class="dsb-lc-skill">' + esc(e.skill) + "</span>" +
2696
- '<span class="dsb-lc-fail">' + e.fails + "</span>" +
2697
- '<span class="dsb-lc-rate"><span class="' + lv.cls + '">' + lv.label + "</span></span>" +
2698
- '<span class="dsb-lc-date">' + esc(e.lastDate || "—") + "</span>" +
2699
- '<span class="dsb-lc-status ' + statusCls + '">' + esc(status) + "</span>" +
2700
- "</div>";
2701
- }
2702
- fh += "</div>";
2703
-
2704
- fh += '<div class="dsb-lsec"><span>🤖 ' + esc(T("lstatAuto")) + '</span><span class="sub">' +
2705
- esc(T("lstatAutoTip")) + " · " + auto.length + " 条</span></div>";
2706
3795
  if (!auto.length) {
2707
3796
  fh += '<div class="dsb-empty" style="padding:12px 0">暂无自动记录 — 插件会记录每次技能工具调用结果(成功✅ / 失败❌)</div>';
2708
3797
  } else {
@@ -2756,13 +3845,13 @@ window.__ModuleLoader__.load({
2756
3845
  var ballCb = $("dsb-set-ball"), fontCb = $("dsb-set-font");
2757
3846
  if (ballCb) ballCb.checked = readStore(BALL_VIS_KEY, true);
2758
3847
  if (fontCb) fontCb.checked = readStore(FONT_BALL_VIS_KEY, true);
2759
- try { fillSkinSelect($("dsb-set-skin")); } catch (eSkin) {}
3848
+ try { } catch (eSkin) {}
2760
3849
  }
2761
3850
 
2762
3851
  // ---- 球体样式 + 字体球统一样式 + 显隐规则(纯 CSS,永久生效)----
2763
3852
  var CSS = [
2764
3853
  /* 技能球 */
2765
- '#' + BALL_ID + '{position:fixed;right:74px;bottom:18px;width:46px;height:46px;border-radius:50%;background:radial-gradient(circle at 50% 60%, rgba(255,255,255,.98) 0%, rgba(214,232,247,.9) 9%, rgba(111,158,232,.7) 18%, rgba(165,46,255,.5) 32%, rgba(206,44,203,.35) 46%, rgba(10,14,30,.9) 66%, rgba(4,6,14,.99) 100%);box-shadow:0 0 22px rgba(111,158,232,.45),0 0 4px rgba(206,44,203,.3),0 4px 18px rgba(0,0,0,.55),inset 0 1px 3px rgba(255,255,255,.4),inset 0 -8px 16px rgba(206,44,203,.14),inset 0 0 14px rgba(80,120,255,.2);color:#ffffff;font-size:15px;font-weight:800;display:flex;align-items:center;justify-content:center;text-shadow:0 0 4px rgba(255,255,255,.95),0 0 10px rgba(160,200,255,.9),0 0 20px rgba(120,90,255,.7),0 2px 4px rgba(10,20,60,.8);filter:drop-shadow(0 0 6px rgba(180,210,255,.8));animation:dsbGlow 2.4s ease-in-out infinite;cursor:grab;z-index:2147483600;user-select:none;touch-action:none;animation:orbBreathe 3.2s ease-in-out infinite;}',
3854
+ '#' + BALL_ID + '{position:fixed;right:62px;bottom:18px;width:34px;height:34px;border-radius:50%;background:radial-gradient(circle at 50% 60%, rgba(255,255,255,.98) 0%, rgba(214,232,247,.9) 9%, rgba(111,158,232,.7) 18%, rgba(165,46,255,.5) 32%, rgba(206,44,203,.35) 46%, rgba(10,14,30,.9) 66%, rgba(4,6,14,.99) 100%);box-shadow:0 0 22px rgba(111,158,232,.45),0 0 4px rgba(206,44,203,.3),0 4px 18px rgba(0,0,0,.55),inset 0 1px 3px rgba(255,255,255,.4),inset 0 -8px 16px rgba(206,44,203,.14),inset 0 0 14px rgba(80,120,255,.2);color:#ffffff;font-size:11px;font-weight:800;display:flex;align-items:center;justify-content:center;text-shadow:0 0 4px rgba(255,255,255,.95),0 0 10px rgba(160,200,255,.9),0 0 20px rgba(120,90,255,.7),0 2px 4px rgba(10,20,60,.8);filter:drop-shadow(0 0 6px rgba(180,210,255,.8));animation:dsbGlow 2.4s ease-in-out infinite;cursor:grab;z-index:2147483600;user-select:none;touch-action:none;animation:orbBreathe 3.2s ease-in-out infinite;}',
2766
3855
  '#' + BALL_ID + ':hover{transform:scale(1.08)}',
2767
3856
  '#' + BALL_ID + '::before{content:"";position:absolute;inset:1px;border-radius:50%;z-index:1;background:radial-gradient(ellipse 85% 26% at 50% 60%, rgba(255,255,255,1) 0%, rgba(235,245,255,.85) 18%, rgba(160,200,255,.5) 38%, rgba(190,120,255,.35) 55%, transparent 75%),radial-gradient(ellipse 70% 18% at 45% 42%, rgba(111,158,232,.55) 0%, transparent 70%),radial-gradient(ellipse 70% 18% at 55% 74%, rgba(206,44,203,.4) 0%, transparent 70%);filter:blur(1px);animation:bandFlow 4.5s ease-in-out infinite;}',
2768
3857
  '#' + BALL_ID + '::after{content:"";position:absolute;inset:-1px;border-radius:50%;z-index:1;background:conic-gradient(from 200deg, rgba(255,255,255,.55), rgba(140,180,255,.35), rgba(206,44,203,.4), rgba(255,255,255,.15), rgba(111,158,232,.4), rgba(255,255,255,.55));-webkit-mask:radial-gradient(circle, transparent 63%, #000 66%, #000 72%, transparent 76%);mask:radial-gradient(circle, transparent 63%, #000 66%, #000 72%, transparent 76%);animation:rimSpin 7s linear infinite;filter:blur(.5px);}',
@@ -2771,14 +3860,18 @@ window.__ModuleLoader__.load({
2771
3860
  '@keyframes orbBreathe{0%,100%{transform:scale(.96);filter:brightness(.92)}50%{transform:scale(1.05);filter:brightness(1.12)}}',
2772
3861
  '@keyframes dsbGlow{0%,100%{text-shadow:0 0 4px rgba(255,255,255,.95),0 0 10px rgba(160,200,255,.9),0 0 20px rgba(120,90,255,.7)}50%{text-shadow:0 0 6px rgba(255,255,255,1),0 0 16px rgba(190,220,255,1),0 0 32px rgba(140,110,255,.9)}}',
2773
3862
  '#' + BALL_ID + ':hover{transform:scale(1.08)}',
2774
- '#' + BALL_ID + ' .dsb-orb-fallback{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:17px;color:#9aa1b5;z-index:1}',
3863
+ '#' + BALL_ID + ' .dsb-orb-fallback{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:12px;color:#9aa1b5;z-index:1}',
2775
3864
  '#' + BALL_ID + ':hover{transform:scale(1.08)}',
2776
3865
  '#' + BALL_ID + '.dsb-ghost{width:12px!important;height:12px!important;font-size:0!important;right:10px!important;bottom:10px!important;opacity:.3}',
2777
3866
  '#' + BALL_ID + '.dsb-ghost:hover{opacity:.8}',
2778
3867
  /* 字体球视觉:v1.5.9 起 font-enhancer 自带暖色极光玻璃样式,本插件不再代管其外观(避免覆盖);显隐控制见下方 data-dsb-font-hidden 规则 */
2779
3868
  /* 显隐开关:取消勾选=彻底隐藏球(display:none),勾选=显示原版 46px 大球 */
2780
3869
  '[data-dsb-ball-hidden="1"] #' + BALL_ID + '{display:none!important}',
2781
- '[data-dsb-font-hidden="1"] #dsh-fe-toggle{display:none!important}'
3870
+ '[data-dsb-font-hidden="1"] #dsh-fe-toggle{display:none!important}',
3871
+ /* 顶部 40px 是 DSH 窗口拖拽带,必须挖掉 */
3872
+ '#'+BALL_ID+',#'+PANEL_ID+',.dsb-skinmenu{-webkit-app-region:no-drag;app-region:no-drag}',
3873
+ '#'+BALL_ID+'{background:#171c2b!important;text-shadow:none!important;filter:none!important;box-shadow:0 4px 14px rgba(0,0,0,.42)!important}',
3874
+ '#'+BALL_ID+'::before{content:none!important;display:none!important}',
2782
3875
  ].join('\n');
2783
3876
 
2784
3877
  function restoreBallPos(ball) {
@@ -2812,39 +3905,6 @@ window.__ModuleLoader__.load({
2812
3905
 
2813
3906
  // v1.5.3 一键恢复默认位置:清位置记忆与收起/隐藏状态,球立即回右下角默认位。
2814
3907
  // 供 DSH 设置页「悬浮球导航」分区按钮调用(球可能被拖出屏幕外卡住,无法点回)。
2815
- // v1.5.7 推荐位安装徽标:探测三个插件的 client 运行状态,注入「已安装/未安装」徽标。
2816
- function recBadgeInstalled(id) {
2817
- if (id === "font") return !!document.getElementById("dsh-fe-toggle");
2818
- if (id === "todoball") return isTodoBallInstalled();
2819
- if (id === "skillbrowser") return !!document.getElementById(BALL_ID);
2820
- return false; // lightbox 用异步 fetch 判定,不走这里
2821
- }
2822
- function markRecBadge(recId, installed) {
2823
- var row = document.querySelector('[data-rec-id="' + recId + '"]');
2824
- if (!row || row.querySelector(".fe-rec-badge")) return;
2825
- var badge = document.createElement("span");
2826
- badge.className = "fe-rec-badge";
2827
- badge.textContent = installed ? "已安装" : "未安装";
2828
- badge.style.cssText = "flex-shrink:0;margin-left:8px;font-size:11px;padding:2px 8px;border-radius:6px;" +
2829
- (installed ? "background:rgba(120,220,150,.15);border:1px solid rgba(120,220,150,.4);color:#9fe0b5"
2830
- : "background:rgba(255,120,120,.15);border:1px solid rgba(255,120,120,.35);color:#ffb3b3") +
2831
- ";white-space:nowrap";
2832
- var star = row.querySelector("a");
2833
- if (star) row.insertBefore(badge, star); else row.appendChild(badge);
2834
- }
2835
- function applyRecBadges() {
2836
- ["font", "skillbrowser", "lightbox"].forEach(function (id) {
2837
- if (id === "lightbox") {
2838
- try {
2839
- fetch("/api/image-gallery/list").then(function (r) {
2840
- markRecBadge("lightbox", r.ok);
2841
- }).catch(function () { markRecBadge("lightbox", false); });
2842
- } catch (e) { markRecBadge("lightbox", false); }
2843
- } else {
2844
- markRecBadge(id, recBadgeInstalled(id));
2845
- }
2846
- });
2847
- }
2848
3908
  function resetBallToDefault() {
2849
3909
  try { localStorage.removeItem(BALL_POS_KEY); } catch (e) {}
2850
3910
  try { writeStore(BALL_VIS_KEY, true); } catch (e) {}
@@ -2917,6 +3977,7 @@ window.__ModuleLoader__.load({
2917
3977
  ball.style.top = (drag.oy + dy) + "px";
2918
3978
  ball.style.right = "auto";
2919
3979
  ball.style.bottom = "auto";
3980
+ if (S.open) positionPanelNearBall(); // m04874 拖球时面板跟随
2920
3981
  });
2921
3982
  function endDrag() {
2922
3983
  if (!drag.active) return;
@@ -2955,9 +4016,44 @@ window.__ModuleLoader__.load({
2955
4016
  // ---- unified ball skin system (shared 128 skins, three-ball sync) ----
2956
4017
  var SKIN = window.__DSH_BALL_SKINS || null;
2957
4018
  var SKIN_STYLE_ID = "dsb-skin-style";
4019
+
4020
+ // ---------- 三球皮肤同步开关 (m04045/m04323) ----------
4021
+ var SYNC_ON_KEY = "dsh-ball-skin-sync-skill";
4022
+ var SOLO_ID_KEY = "dsh-ball-skin-solo-skill";
4023
+ function isSyncOn() {
4024
+ try { var v = localStorage.getItem(SYNC_ON_KEY); return v === null ? true : v !== "0"; } catch (e) { return true; }
4025
+ }
4026
+ function setSyncOn(on) {
4027
+ try {
4028
+ localStorage.setItem(SYNC_ON_KEY, on ? "1" : "0");
4029
+ if (on) { var g = localStorage.getItem(SKIN.KEY); if (g) localStorage.setItem(SOLO_ID_KEY, g); }
4030
+ else if (!localStorage.getItem(SOLO_ID_KEY)) { var g2 = localStorage.getItem(SKIN.KEY); if (g2) localStorage.setItem(SOLO_ID_KEY, g2); }
4031
+ } catch (e) {}
4032
+ }
4033
+ function soloResolve(id) {
4034
+ if (!SKIN) return null;
4035
+ if (!id || id === "auto") return SKIN.resolve("auto");
4036
+ return SKIN.BY_ID[id] ? id : SKIN.resolve("auto");
4037
+ }
4038
+ function mySkinId() {
4039
+ if (!SKIN) return null;
4040
+ if (isSyncOn()) return SKIN.getId();
4041
+ try { return soloResolve(localStorage.getItem(SOLO_ID_KEY) || (SKIN.getPref ? SKIN.getPref() : "")) || SKIN.getId(); } catch (e) { return SKIN.getId(); }
4042
+ }
4043
+ function mySkinPref() {
4044
+ if (!SKIN) return "";
4045
+ if (isSyncOn()) return SKIN.getPref ? SKIN.getPref() : "";
4046
+ try { return localStorage.getItem(SOLO_ID_KEY) || (SKIN.getPref ? SKIN.getPref() : ""); } catch (e) { return ""; }
4047
+ }
4048
+ function pickSkin(id) {
4049
+ if (!SKIN) return;
4050
+ if (isSyncOn()) { SKIN.setSkin(id); return; }
4051
+ try { localStorage.setItem(SOLO_ID_KEY, id); } catch (e) {}
4052
+ try { applySkinNow(); } catch (e) {}
4053
+ }
2958
4054
  function skinBallCss() {
2959
4055
  if (!SKIN) return "";
2960
- var id = SKIN.getId();
4056
+ var id = mySkinId();
2961
4057
  var s = SKIN.BY_ID[id];
2962
4058
  if (!s) return "";
2963
4059
  var B = "#dsh-skill-browser-ball", BAND = B + "::before", RIM = B + "::after";
@@ -2969,6 +4065,9 @@ window.__ModuleLoader__.load({
2969
4065
  css += "\n@keyframes dshSkinSpin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}\n@keyframes dshSkinSpinR{0%{transform:rotate(0)}100%{transform:rotate(-360deg)}}";
2970
4066
  css += "\n" + SKIN.fx(id, { B: B, BAND: BAND, RIM: RIM });
2971
4067
  if (s.rim) css += "\n" + RIM + "{content:\"\"}";
4068
+ css += "\n" + B + "{background:var(--pbgA,#171c2b)!important;text-shadow:none!important;filter:none!important;box-shadow:0 4px 14px rgba(0,0,0,.42)!important}";
4069
+ css += "\n" + BAND + "{content:none!important;display:none!important}";
4070
+ css += "\n" + RIM + "{content:\"\"!important;display:block!important;position:absolute!important;inset:-1px!important;border-radius:50%!important;background:conic-gradient(from 200deg,var(--stA,#8fd3c0),transparent 30%,var(--stA,#8fd3c0) 55%,transparent 80%,var(--stA,#8fd3c0))!important;-webkit-mask:radial-gradient(circle,transparent 62%,#000 66%,#000 73%,transparent 77%)!important;mask:radial-gradient(circle,transparent 62%,#000 66%,#000 73%,transparent 77%)!important;animation:rimSpin 7s linear infinite!important;pointer-events:none!important}";
2972
4071
  return css;
2973
4072
  }
2974
4073
  // ---- v1.9.2: semantic colour vars so every new UI follows the active skin ----
@@ -3192,7 +4291,7 @@ window.__ModuleLoader__.load({
3192
4291
  function skinPanelCss() {
3193
4292
  if (!SKIN) return "";
3194
4293
  var P = "#dsh-skill-browser-panel";
3195
- var id = SKIN.getId();
4294
+ var id = mySkinId();
3196
4295
  var css = SKIN.varsCss(P, id);
3197
4296
  css += "\n" + semanticVarsCss(P, id);
3198
4297
  if (skinIsLight(id)) {
@@ -3327,7 +4426,8 @@ window.__ModuleLoader__.load({
3327
4426
  }
3328
4427
  // 原版皮肤(仅本球):flag 置位时清空皮肤覆盖样式,技能球回到原始星云紫。
3329
4428
  var SKIN_CLASSIC_KEY = "dsh-ball-skin-classic-skill";
3330
- function isSkinClassic() { try { return localStorage.getItem(SKIN_CLASSIC_KEY) === "1"; } catch (e) { return false; } }
4429
+ // 原版皮肤已于 2026-10-29 按用户要求彻底移除;旧标记自动清理,始终返回 false。
4430
+ function isSkinClassic() { try { localStorage.removeItem(SKIN_CLASSIC_KEY); } catch (e) {} return false; }
3331
4431
  function setSkinClassic(on) {
3332
4432
  try { if (on) localStorage.setItem(SKIN_CLASSIC_KEY, "1"); else localStorage.removeItem(SKIN_CLASSIC_KEY); } catch (e) {}
3333
4433
  applySkinNow();
@@ -3340,12 +4440,11 @@ window.__ModuleLoader__.load({
3340
4440
  document.head.appendChild(el); // (re)append last so overrides win the cascade
3341
4441
  ensureSkinMenuCss();
3342
4442
  refreshSkinMenu();
3343
- fillSkinSelect(document.getElementById("dsb-set-skin"));
3344
4443
  fillSkinSelect(document.getElementById("dsh-set-skin-global"));
3345
4444
  }
3346
4445
  function skinOptionsHtml() {
3347
4446
  if (!SKIN) return "";
3348
- var html = "", groups = SKIN.groups();
4447
+ var html = '<option value="auto">0 \u00b7 \u8ddf\u968f\u7cfb\u7edf \u00b7 \u6df1/\u6d45\u81ea\u52a8</option>', groups = SKIN.groups();
3349
4448
  for (var g = 0; g < groups.length; g++) {
3350
4449
  html += '<optgroup label="' + groups[g] + '">';
3351
4450
  for (var i = 0; i < SKIN.ALL.length; i++) {
@@ -3360,11 +4459,28 @@ window.__ModuleLoader__.load({
3360
4459
  function fillSkinSelect(sel) {
3361
4460
  if (!sel || !SKIN) return;
3362
4461
  if (!sel.options.length) {
3363
- sel.innerHTML = '<option value="classic">原版 · 星云紫(仅本球)</option>' + skinOptionsHtml();
4462
+ sel.innerHTML = '<option value="auto">0 · 跟随系统 · 深/浅自动</option>' + skinOptionsHtml();
3364
4463
  }
3365
- sel.value = isSkinClassic() ? "classic" : SKIN.getId();
4464
+ sel.value = mySkinPref() === "auto" ? "auto" : mySkinId();
3366
4465
  }
3367
4466
  // ---- todo-style grouped skin menu (panel 🎨) ----
4467
+ var DSB_ICONS = {
4468
+ plug: '<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.55" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 1.9v3.1M10 1.9v3.1"/><path d="M4.4 5h7.2v2.3a3.6 3.6 0 0 1-7.2 0z"/><path d="M8 10.9v3.2"/></svg>',
4469
+ palette: '<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 14.2A6.2 6.2 0 1 1 14.2 8c0 1.1-.9 1.8-2 1.8h-1.4c-.9 0-1.6.7-1.6 1.6 0 .4.2.8.4 1.1.3.4.4.7.4 1 0 .4-.4.7-1 .7z"/><circle cx="5.6" cy="6.2" r=".85"/><circle cx="8" cy="4.4" r=".85"/><circle cx="10.4" cy="6.2" r=".85"/></svg>',
4470
+ gear: '<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="8" cy="8" r="4.6"/><circle cx="8" cy="8" r="1.9"/><path d="M8 3.4v1.4M8 11.2v1.4M3.4 8h1.4M11.2 8h1.4M4.6 4.6l1 1M10.4 10.4l1 1M11.4 4.6l-1 1M5.6 10.4l-1 1"/></svg>',
4471
+ bolt: '<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round" aria-hidden="true"><path d="M8.8 1.8 3.8 9h3.4l-.9 5.2L11.9 7H8.5z"/></svg>',
4472
+ refresh: '<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M13.2 8a5.2 5.2 0 1 1-1.6-3.8"/><path d="M13.4 1.8v3h-3"/></svg>',
4473
+ star: '<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 1.8l1.9 4 4.3.5-3.2 3 .9 4.3L8 11.4l-3.9 2.2.9-4.3-3.2-3 4.3-.5z"/></svg>',
4474
+ close: '<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" aria-hidden="true"><path d="M3.5 3.5l9 9M12.5 3.5l-9 9"/></svg>',
4475
+ book: "<svg viewBox=\"0 0 16 16\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.55\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M8 3.4C7 2.6 5.6 2.2 3.8 2.2c-.6 0-1.2.05-1.8.16V12.6c.6-.11 1.2-.16 1.8-.16 1.8 0 3.2.44 4.2 1.2 1-.76 2.4-1.2 4.2-1.2.6 0 1.2.05 1.8.16V2.36a10.5 10.5 0 00-1.8-.16C10.4 2.2 9 2.6 8 3.4z\"/><path d=\"M8 3.4v10.24\"/></svg>",
4476
+ folder: "<svg viewBox=\"0 0 16 16\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.55\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M2.2 4.7c0-.5.4-.9.9-.9h2.9l1.3 1.5h5.6c.5 0 .9.4.9.9v6.3c0 .5-.4.9-.9.9H3.1c-.5 0-.9-.4-.9-.9z\"/></svg>",
4477
+ search: "<svg viewBox=\"0 0 16 16\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.55\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"7.1\" cy=\"7.1\" r=\"4.2\"/><path d=\"M10.3 10.3l3.1 3.1\"/></svg>",
4478
+ eye: "<svg viewBox=\"0 0 16 16\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.55\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M1.9 8s2.4-4.1 6.1-4.1S14.1 8 14.1 8s-2.4 4.1-6.1 4.1S1.9 8 1.9 8z\"/><circle cx=\"8\" cy=\"8\" r=\"1.8\"/></svg>",
4479
+ chart: "<svg viewBox=\"0 0 16 16\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.55\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M2.7 2.6v10.8h10.6\"/><path d=\"M5.4 11V8.2M8.2 11V5.4M11 11V7\"/></svg>",
4480
+ folders: "<svg viewBox=\"0 0 16 16\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.55\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M2.4 5.4c0-.5.4-.9.9-.9h2.6l1.2 1.4h4.2c.5 0 .9.4.9.9v4.9c0 .5-.4.9-.9.9H3.3c-.5 0-.9-.4-.9-.9z\"/><path d=\"M5 3.2h2.5l1.1 1.2\"/></svg>",
4481
+ grid: "<svg viewBox=\"0 0 16 16\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.55\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"2.6\" y=\"2.6\" width=\"4.3\" height=\"4.3\" rx=\"1\"/><rect x=\"9.1\" y=\"2.6\" width=\"4.3\" height=\"4.3\" rx=\"1\"/><rect x=\"2.6\" y=\"9.1\" width=\"4.3\" height=\"4.3\" rx=\"1\"/><rect x=\"9.1\" y=\"9.1\" width=\"4.3\" height=\"4.3\" rx=\"1\"/></svg>"
4482
+ };
4483
+ function dsbGico(n) { return '<span class="dsb-gico">' + (DSB_ICONS[n] || '') + '</span>'; }
3368
4484
  var SKINMENU_STYLE_ID = "dsb-skinmenu-style";
3369
4485
  var SKINMENU_MARGIN = "0 16px 10px";
3370
4486
  function skinMenuHtml() {
@@ -3373,21 +4489,27 @@ window.__ModuleLoader__.load({
3373
4489
  var a = (s.vars && s.vars.stA) || "#888", b = (s.vars && s.vars.bgB) || "#222";
3374
4490
  return '<span class="dot" style="background:linear-gradient(135deg,' + a + ',' + b + ')"></span>';
3375
4491
  }
3376
- var html = '<div class="cap">原版</div>';
3377
- html += '<div class="row' + (isSkinClassic() ? " cur" : "") + '" data-skin-classic="1">' +
3378
- '<span class="chk">' + (isSkinClassic() ? "✓" : "") + '</span>' +
3379
- '<span class="dot" style="background:linear-gradient(135deg,#a5b8fc,#3c1e8f)"></span>' +
3380
- "<span>原版 · 星云紫(仅本球)</span></div>";
4492
+ var html = "";
4493
+ var _auto = mySkinPref() === "auto";
4494
+ var _sync = isSyncOn();
4495
+ html += '<div class="row row-wide sync-row' + (_sync ? " sync-on" : "") + '" data-sync="1" title="\u52fe\u9009 = \u8ddf\u968f\u5168\u5c40\u76ae\u80a4\uff1b\u4e0d\u52fe = \u672c\u7403\u72ec\u7acb\u76ae\u80a4">' +
4496
+ '<span class="chk">' + (_sync ? "\u2713" : "") + '</span>' +
4497
+ "<span>\u4e09\u7403\u76ae\u80a4\u540c\u6b65</span></div>";
4498
+ html += '<div class="cap">\u8ddf\u968f\u7cfb\u7edf</div>';
4499
+ html += '<div class="row row-wide' + (_auto ? " cur" : "") + '" data-skin-id="auto" title="跟随客户端深/浅色自动切换">' +
4500
+ '<span class="chk">' + (_auto ? "\u2713" : "") + '</span>' +
4501
+ '<span class="dot" style="background:linear-gradient(135deg,#f9fafb,#151517)"></span>' +
4502
+ "<span class=\"nm\">0 \u00b7 \u968f\u7cfb\u7edf \u00b7 \u6df1/\u6d45\u81ea\u52a8</span></div>";
3381
4503
  var groups = SKIN.groups();
3382
4504
  for (var g = 0; g < groups.length; g++) {
3383
4505
  html += '<div class="cap">' + groups[g] + "</div>";
3384
4506
  for (var i = 0; i < SKIN.ALL.length; i++) {
3385
4507
  var s = SKIN.ALL[i];
3386
4508
  if (s.group !== groups[g]) continue;
3387
- var cur = !isSkinClassic() && s.id === SKIN.getId();
4509
+ var cur = !isSkinClassic() && !_auto && s.id === mySkinId();
3388
4510
  html += '<div class="row' + (cur ? " cur" : "") + '" data-skin-id="' + s.id + '" title="' + s.en + ' (方案 ' + s.no + ')">' +
3389
4511
  '<span class="chk">' + (cur ? "✓" : "") + "</span>" + dot(s) +
3390
- "<span>" + s.no + " · " + s.name + "</span></div>";
4512
+ "<span class=\"nm\">" + s.no + " · " + s.name + "</span></div>";
3391
4513
  }
3392
4514
  }
3393
4515
  return html;
@@ -3420,11 +4542,12 @@ window.__ModuleLoader__.load({
3420
4542
  var row = t.closest("#dsb-skin .row");
3421
4543
  if (!row) return;
3422
4544
  ev.stopPropagation();
4545
+ if (row.getAttribute("data-sync")) { setSyncOn(!isSyncOn()); applySkinNow(); refreshSkinMenu(); return; }
3423
4546
  if (row.getAttribute("data-skin-classic")) { setSkinClassic(true); return; }
3424
4547
  var id = row.getAttribute("data-skin-id");
3425
- if (id) { setSkinClassic(false); SKIN.setSkin(id); }
4548
+ if (id) { setSkinClassic(false); pickSkin(id); }
3426
4549
  }, true);
3427
- var SKINMENU_CSS = ".dsb-skinmenu,.fe-skinmenu{display:none;max-height:46vh;overflow-y:auto;border:1px solid rgba(255,255,255,.12);border-radius:12px;background:rgba(10,13,20,.98);padding:6px;z-index:5;box-shadow:0 14px 40px rgba(0,0,0,.5)}\n.dsb-skinmenu.open,.fe-skinmenu.open{display:block}\n.dsb-skinmenu .cap,.fe-skinmenu .cap{font-size:10.5px;font-weight:700;color:#8b93a8;padding:6px 8px 2px;letter-spacing:1px}\n.dsb-skinmenu .row,.fe-skinmenu .row{display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:8px;cursor:pointer;font-size:12px;color:#dfe4ec;font-family:'Segoe UI','Microsoft YaHei','PingFang SC',system-ui,sans-serif}\n.dsb-skinmenu .row:hover,.fe-skinmenu .row:hover{background:rgba(255,255,255,.08)}\n.dsb-skinmenu .row.cur,.fe-skinmenu .row.cur{color:var(--dsb-warn,#ffd27a);font-weight:600}\n.dsb-skinmenu .dot,.fe-skinmenu .dot{width:10px;height:10px;border-radius:50%;flex-shrink:0;border:1px solid rgba(255,255,255,.2)}\n.dsb-skinmenu .chk,.fe-skinmenu .chk{width:14px;text-align:center;flex-shrink:0}";
4550
+ var SKINMENU_CSS = ".dsb-skinmenu{display:none;max-height:46vh;overflow-y:auto;border:1px solid rgba(255,255,255,.12);border-radius:12px;background:rgba(10,13,20,.98);padding:6px;z-index:5;box-shadow:0 14px 40px rgba(0,0,0,.5)}\n.dsb-skinmenu{max-width:none!important;width:auto}.dsb-skinmenu.open{display:grid!important;grid-template-columns:repeat(8,minmax(0,1fr))!important;gap:5px 3px;align-content:start}\n.dsb-skinmenu .cap{font-size:11px;font-weight:700;color:#9aa3b8;padding:7px 8px 3px;letter-spacing:1px}\n.dsb-skinmenu .cap{grid-column:1/-1}\n.dsb-skinmenu .row-wide{grid-column:1/-1}\n.dsb-skinmenu .row{display:flex;align-items:center;gap:4px;padding:5px 3px;min-height:26px;line-height:1.3;border-radius:8px;cursor:pointer;font-size:11px;white-space:nowrap;overflow:hidden;color:#dfe4ec;font-family:'Segoe UI','Microsoft YaHei','PingFang SC',system-ui,sans-serif;min-width:0}\n.dsb-skinmenu .sync-row{background:rgba(255,255,255,.06);font-weight:600;margin-bottom:2px}\n.dsb-skinmenu .sync-row.sync-on{color:#ffd27a}\n.dsb-skinmenu .row:hover{background:rgba(255,255,255,.08)}\n.dsb-skinmenu .row.cur{color:var(--dsb-warn,#ffd27a);font-weight:600}\n.dsb-skinmenu .dot{width:7px;height:7px;border-radius:50%;flex-shrink:0;border:1px solid rgba(255,255,255,.2)}\n.dsb-skinmenu .chk{width:12px;text-align:center;flex-shrink:0}\n.dsb-skinmenu .nm{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}";
3428
4551
  function ensureSkinMenuMargin() { var m = skinMenuEl(); if (m) m.style.margin = SKINMENU_MARGIN; }
3429
4552
 
3430
4553
  if (SKIN) {
@@ -3438,12 +4561,6 @@ window.__ModuleLoader__.load({
3438
4561
  document.addEventListener("change", function (ev) {
3439
4562
  var t = ev.target;
3440
4563
  if (!t || !t.id) return;
3441
- if (t.id === "dsb-set-skin") {
3442
- if (t.value === "classic") { setSkinClassic(true); return; }
3443
- setSkinClassic(false);
3444
- SKIN.setSkin(t.value);
3445
- return;
3446
- }
3447
4564
  if (t.id === "dsh-set-skin-global") {
3448
4565
  // 全局三球统一换肤:清掉本球与字体球的「原版」标记
3449
4566
  try { localStorage.removeItem(SKIN_CLASSIC_KEY); } catch (e) {}
@@ -3480,90 +4597,87 @@ window.__ModuleLoader__.load({
3480
4597
  if (!React) return null;
3481
4598
 
3482
4599
  var rows = [];
3483
- // 未安装的球:开关仍显示但置灰+标注,保证"单独装任何一个,分区界面一致"(生态规格)
4600
+ // ── 简约版悬浮球导航(m05874:去须知块、去说明文字,只留开关/皮肤/复位/作者插件)──
3484
4601
  function mkToggle(id, label, onChange, installed) {
3485
4602
  var on = installed !== false;
3486
- return React.createElement("label", { key: id, style: { display: "flex", alignItems: "center", gap: "8px", fontSize: "13px", cursor: on ? "pointer" : "not-allowed", opacity: on ? 1 : .5 } },
4603
+ return React.createElement("label", { key: id, style: { display: "flex", alignItems: "center", gap: "8px", fontSize: "12.5px", cursor: on ? "pointer" : "not-allowed", opacity: on ? 1 : .45, lineHeight: "1.6" } },
3487
4604
  React.createElement("input", { type: "checkbox", id: id, disabled: !on, onChange: onChange }),
3488
4605
  React.createElement("span", null, label + (on ? "" : "(未安装)")));
3489
4606
  }
3490
- rows.push(React.createElement("div", { key: "head", style: { fontWeight: "600", marginBottom: "4px" } }, "悬浮球导航"));
3491
- rows.push(mkToggle("dsb-set-ball", "技能浏览器悬浮球(🧩)", function (e) {
4607
+ rows.push(React.createElement("div", { key: "head", style: { fontWeight: "600", fontSize: "13px", marginBottom: "2px" } }, "悬浮球"));
4608
+ rows.push(mkToggle("dsb-set-ball", "技能与 MCP 浏览器", function (e) {
3492
4609
  writeStore(BALL_VIS_KEY, e.target.checked);
3493
4610
  applyBallVisibility();
3494
4611
  if (!e.target.checked && S.open) closePanel();
3495
4612
  }, true));
3496
- rows.push(mkToggle("dsb-set-font", "字体插件悬浮球(🔤)", function (e) {
4613
+ rows.push(mkToggle("dsb-set-font", "字体插件", function (e) {
3497
4614
  writeStore(FONT_BALL_VIS_KEY, e.target.checked);
3498
4615
  applyFontBallVisibility();
3499
4616
  }, isFontEnhancerInstalled()));
3500
- rows.push(mkToggle("dsb-set-todo", "Todo 悬浮球(📋)", function (e) {
4617
+ rows.push(mkToggle("dsb-set-todo", "Todo 进度", function (e) {
3501
4618
  writeStore(TODO_BALL_VIS_KEY, e.target.checked);
3502
4619
  if (typeof applyTodoBallVisibility === "function") applyTodoBallVisibility();
3503
4620
  var el = document.getElementById("dsh-tfb-root");
3504
4621
  if (el) el.style.setProperty("display", e.target.checked ? "" : "none", "important");
3505
4622
  }, isTodoBallInstalled()));
3506
- // 悬浮球皮肤(三球同步):<select> 原生控件,不依赖 React hooks
4623
+ // 皮肤(三球同步)
3507
4624
  (function () {
3508
4625
  var SKG = window.__DSH_BALL_SKINS || null;
3509
4626
  if (!SKG || !React) return;
3510
- var optgroups = [];
4627
+ var optgroups = [React.createElement("option", { key: "auto", value: "auto" }, "0 \u00b7 \u8ddf\u968f\u7cfb\u7edf \u00b7 \u6df1/\u6d45\u81ea\u52a8")];
3511
4628
  var groups = SKG.groups();
3512
4629
  for (var g = 0; g < groups.length; g++) {
3513
4630
  var opts = [];
3514
4631
  for (var i = 0; i < SKG.ALL.length; i++) {
3515
4632
  var sp = SKG.ALL[i];
3516
4633
  if (sp.group !== groups[g]) continue;
3517
- opts.push(React.createElement("option", { key: sp.id, value: sp.id }, "方案" + sp.no + " · " + sp.name));
4634
+ opts.push(React.createElement("option", { key: sp.id, value: sp.id }, sp.no + " \u00b7 " + sp.name));
3518
4635
  }
3519
4636
  optgroups.push(React.createElement("optgroup", { key: groups[g], label: groups[g] }, opts));
3520
4637
  }
3521
- rows.push(React.createElement("div", { key: "skin-row", style: { display: "flex", alignItems: "center", gap: "8px", fontSize: "13px" } },
3522
- React.createElement("span", null, "悬浮球皮肤"),
4638
+ rows.push(React.createElement("div", { key: "skin-row", style: { display: "flex", alignItems: "center", gap: "8px", fontSize: "12.5px" } },
4639
+ React.createElement("span", { style: { flexShrink: 0 } }, "皮肤"),
3523
4640
  React.createElement("select", {
3524
4641
  id: "dsh-set-skin-global",
3525
- defaultValue: SKG.getId(),
4642
+ defaultValue: (SKG.getPref && SKG.getPref() === "auto") ? "auto" : SKG.getId(),
3526
4643
  onChange: function (e) { SKG.setSkin(e.target.value); },
3527
- style: { flex: "1", minWidth: "200px", background: "rgba(0,0,0,.35)", border: "1px solid rgba(255,255,255,.14)", borderRadius: "6px", color: "inherit", fontSize: "12px", padding: "4px 6px", outline: "none" }
3528
- }, optgroups),
3529
- React.createElement("span", { style: { fontSize: "12px", color: "#8b93a8" } }, "三球同步 · 128 款")
4644
+ style: { flex: "1", minWidth: "0", background: "rgba(0,0,0,.28)", border: "1px solid rgba(255,255,255,.12)", borderRadius: "6px", color: "inherit", fontSize: "12px", padding: "4px 6px", outline: "none" }
4645
+ }, optgroups)
3530
4646
  ));
3531
4647
  })();
3532
- rows.push(React.createElement("div", { key: "hint", style: { fontSize: "12px", color: "#8b93a8" } },
3533
- "悬浮球可拖动;清单面板始终居中;本分区就在 DSH 设置中,随时可改悬浮球显隐"));
3534
- // ── 安装与更新须知(v1.5.7:24h 供应链政策 + 推荐清单说明)──
3535
- rows.push(React.createElement("div", { key: "notice-block", style: { marginTop: "12px", padding: "10px 12px", background: "rgba(255,255,255,.03)", border: "1px solid rgba(255,255,255,.06)", borderRadius: "10px", fontSize: "12px", color: "#9aa1b5", lineHeight: "1.7" } },
3536
- React.createElement("div", { style: { fontWeight: "600", color: "#cfd4dc", marginBottom: "4px" } }, "📌 安装与更新须知"),
3537
- React.createElement("div", null, "1. 本分区由「技能浏览器」提供;技能球 / 字体球 / Todo 球三个悬浮球的显隐都在这里控制(新装默认全部显示)。"),
3538
- React.createElement("div", null, "2. 推荐插件列表实时显示「已安装 / 未安装」状态:绿色「已安装」= 可直接使用;红色「未安装」= 点击 ⭐ Star 前往仓库,或到插件市场搜索安装。"),
3539
- React.createElement("div", { style: { color: "#e0b0b0" } }, "3. 【重要】安装或更新插件时,若报错含 minimumReleaseAge 或 ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION:这不是插件坏了,是 DSH 对刚发布 npm 包的 24 小时安全观察期(防止刚发布的投毒包)。"),
3540
- React.createElement("div", { style: { color: "#e0b0b0" } }, "4. 遇到 24h 报错的处理:等一天再安装/更新即可自动通过,插件本体没有任何问题。插件作者连发多版时窗口期会顺延一天。"),
3541
- React.createElement("div", null, "5. 悬浮球可拖动换位置;拖到屏幕外找不到时,点 🎯 按钮所有悬浮球一起回到右下角。三球已统一 128 款皮肤:在 DSH 设置页本分区的「悬浮球皮肤」下拉切换(三球同步),或在各球面板里切。")
3542
- ));
3543
- // ── 一键恢复默认位置(v1.5.3 用户要求):悬浮球被拖出屏幕外卡住时,
3544
- // 点此按钮立即回到默认右下角 (right:26px bottom:118px) 46px 常规大小,
3545
- // 并顺带恢复显示、解除收起小圆点状态。无需重启。──
3546
4648
  rows.push(React.createElement("button", { key: "reset-pos",
3547
4649
  onClick: function () { try { resetBallToDefault(); } catch (e) {} },
3548
- style: { alignSelf: "flex-start", fontSize: "13px", cursor: "pointer",
3549
- background: "rgba(255,210,122,.12)", border: "1px solid rgba(255,210,122,.35)",
3550
- borderRadius: "6px", padding: "6px 12px", color: "#ffd27a" }
3551
- }, "🎯 所有悬浮球回到右下角"));
3552
- // ── 推荐插件(v1.5.7 统一三条清单:字体/技能/照片,探测式安装徽标)──
3553
- rows.push(React.createElement("div", { key: "rec-head", style: { fontWeight: "600", marginTop: "12px", marginBottom: "4px", borderTop: "1px solid rgba(255,255,255,.08)", paddingTop: "10px" } }, "推荐插件"));
3554
- function recRow(key, recId, icon, text, repo) {
3555
- return React.createElement("div", { key: key, "data-rec-id": recId, style: { display: "flex", alignItems: "center", justifyContent: "space-between", fontSize: "13px", padding: "3px 0" } },
3556
- React.createElement("span", null, icon + " " + text),
3557
- React.createElement("a", { href: "https://github.com/" + repo, target: "_blank", rel: "noopener", style: { textDecoration: "none", fontSize: "12px", background: "rgba(255,255,255,.06)", border: "1px solid rgba(255,255,255,.12)", borderRadius: "6px", padding: "2px 8px", color: "#ffd27a", cursor: "pointer", whiteSpace: "nowrap" } }, "⭐ Star"));
3558
- }
3559
- // ── 推荐插件(v1.5.7 统一清单:字体/技能/照片/Todo,探测式安装徽标)──
3560
- // Todo 悬浮球皮肤切换(三皮肤):事件委托到本分区容器——React 合成
3561
- // 事件与 ref 均不可靠(slot 重渲染会重建 DOM),委托到容器上永不失效。
3562
- // 点击写 localStorage + 直调 todo 插件的 __dshtfbApplyTheme 钩子。
3563
- rows.push(recRow("rec-font", "font", "🔤", "字体插件 — 区域自定义字体/颜色/字号", "loyalchiiina/dsh-font-enhancer"));
3564
- rows.push(recRow("rec-skillbrowser", "skillbrowser", "🧩", "技能浏览器 — 悬浮球面板浏览共享技能库 + 失效台账", "loyalchiiina/dsh-skill-browser"));
3565
- rows.push(recRow("rec-lightbox", "lightbox", "🖼️", "照片显示器 — 聊天图片灯箱(放大/下载/浏览)", "loyalchiiina/dsh-chat-image-lightbox"));
3566
- rows.push(recRow("rec-todoball", "todoball", "📋", "Todo 悬浮球 — todo_write 进度常驻监控 + 多会话固定 + 三皮肤(面板内 🎨 切换)", "loyalchiiina/dsh-todo-float-ball"));return React.createElement("div", { "data-dsb-slot": "1", style: { display: "flex", flexDirection: "column", gap: "8px", padding: "8px 0" } }, rows);
4650
+ style: { alignSelf: "flex-start", fontSize: "12px", cursor: "pointer",
4651
+ background: "rgba(255,210,122,.14)", border: "1px solid rgba(255,210,122,.45)",
4652
+ borderRadius: "6px", padding: "5px 10px", color: "#ffd27a" }
4653
+ }, "\u25ce \u56de\u5230\u53f3\u4e0b\u89d2"));
4654
+ // ── 作者插件(m05874/m05875:全部摆上做宣传)──
4655
+ rows.push(React.createElement("div", { key: "rec-head", style: { fontWeight: "600", fontSize: "12.5px", marginTop: "10px", paddingTop: "9px", borderTop: "1px solid rgba(255,255,255,.08)" } }, "\u4f5c\u8005\u63d2\u4ef6"));
4656
+ function recRow(key, recId, name, desc, repo) {
4657
+ return React.createElement("a", { key: key, "data-rec-id": recId, href: "https://github.com/" + repo, target: "_blank", rel: "noopener",
4658
+ style: { display: "flex", alignItems: "baseline", gap: "8px", textDecoration: "none", color: "inherit", fontSize: "12.5px", padding: "3px 0", opacity: .92 } },
4659
+ React.createElement("span", { style: { fontWeight: "600", flexShrink: 0 } }, name),
4660
+ React.createElement("span", { style: { fontSize: "11.5px", color: "#8b93a8", flex: "1", minWidth: "0", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, desc),
4661
+ React.createElement("span", { style: { fontSize: "11px", color: "#ffd27a", flexShrink: 0 } }, "\u2b50"));
4662
+ }
4663
+ var AUTH = [
4664
+ ["font", "dsh-font-enhancer", "\u5b57\u4f53\u63d2\u4ef6", "\u533a\u57df\u5b57\u4f53/\u989c\u8272/\u5b57\u53f7 DIY"],
4665
+ ["skillbrowser", "dsh-skill-browser", "\u6280\u80fd\u6d4f\u89c8\u5668", "\u6280\u80fd\u5e93\u6d4f\u89c8\u4e0e\u6cbb\u7406"],
4666
+ ["todoball", "dsh-todo-float-ball", "Todo \u8fdb\u5ea6\u7403", "\u4efb\u52a1\u8fdb\u5ea6\u5e38\u9a7b\u76d1\u63a7"],
4667
+ ["lightbox", "dsh-chat-image-lightbox", "\u56fe\u7247\u706f\u7bb1", "\u804a\u5929\u56fe\u7247\u5168\u5c4f\u4e0e\u7f29\u7565\u56fe"],
4668
+ ["voice", "dsh-voice-alert", "\u8bed\u97f3\u63d0\u9192", "\u5b8c\u6210/\u5931\u8d25\u81ea\u52a8\u64ad\u62a5"],
4669
+ ["litfetch", "dsh-lit-fetch", "\u6587\u732e\u68c0\u7d22", "\u6309 DOI/\u5173\u952e\u8bcd\u4e0b PDF"],
4670
+ ["qqdigest", "dsh-qq-digest", "QQ \u7fa4\u6458\u8981", "\u7fa4\u804a\u8bb0\u5f55\u4e00\u952e\u603b\u7ed3"],
4671
+ ["modelFold", "dsh-model-fold", "\u6a21\u578b\u83dc\u5355\u5206\u7ec4", "\u6a21\u578b\u9009\u62e9\u6309\u6765\u6e90\u5f52\u7c7b"],
4672
+ ["archive", "dsh-archive-favorites-forge", "\u5bf9\u8bdd\u5f52\u6863\u52a9\u624b", "\u6536\u85cf/\u6279\u91cf\u5f52\u6863/\u5220\u9664"],
4673
+ ["vision", "dsh-vision", "\u5916\u6302\u8bc6\u56fe", "\u56fe\u7247\u8bc6\u522b\u4e0e\u622a\u56fe"],
4674
+ ["artifact", "dsh-artifact-linker", "\u6587\u4ef6\u70b9\u51fb\u76f4\u8fbe", "\u8def\u5f84\u70b9\u51fb\u6253\u5f00\u6587\u4ef6"],
4675
+ ["webFree", "dsh-deepseek-web-free", "DeepSeek \u7f51\u9875\u7aef", "\u7f51\u9875\u6a21\u578b\u63a5\u5165"],
4676
+ ];
4677
+ for (var ai = 0; ai < AUTH.length; ai++) {
4678
+ rows.push(recRow("rec-" + AUTH[ai][0], AUTH[ai][0], AUTH[ai][2], AUTH[ai][3], "loyalchiiina/" + AUTH[ai][1]));
4679
+ }
4680
+ return React.createElement("div", { "data-dsb-slot": "1", style: { display: "flex", flexDirection: "column", gap: "7px", padding: "6px 0" } }, rows);
3567
4681
  }
3568
4682
 
3569
4683
  function registerSettings(ctx) {
@@ -3637,8 +4751,6 @@ window.__ModuleLoader__.load({
3637
4751
  applyBallVisibility();
3638
4752
  } catch (e) {}
3639
4753
  try { console.log("[dsh-skill-browser] client mounted (v1.7.2: 3-level aggregate switches + batch confirm menu)"); } catch (e3) {}
3640
- setTimeout(applyRecBadges, 1200);
3641
- setInterval(applyRecBadges, 5000);
3642
4754
  };
3643
4755
 
3644
4756
  return module.exports;