dsh-skill-browser 1.9.11 → 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.
- package/CHANGELOG.md +31 -0
- package/README.md +7 -0
- package/lib/client.js +1147 -137
- package/lib/groups.js +171 -21
- package/lib/index.js +141 -2
- package/lib/mcp.js +853 -0
- package/locale/en.json +2 -2
- package/locale/zh.json +2 -2
- package/package.json +3 -3
package/lib/client.js
CHANGED
|
@@ -175,10 +175,12 @@ window.__ModuleLoader__.load({
|
|
|
175
175
|
mounted: false,
|
|
176
176
|
open: false,
|
|
177
177
|
panelBuilt: false,
|
|
178
|
-
view: "skills", // "skills" | "ledger"
|
|
178
|
+
view: "skills", // "skills" | "ledger" | "guide" | "mcp"
|
|
179
179
|
data: null,
|
|
180
180
|
ledger: null,
|
|
181
181
|
ledgerDetail: null,
|
|
182
|
+
// ── v2.0.0 MCP 管理 ──
|
|
183
|
+
mcp: null, // { data, stats, loading, statsLoading, error, detailId, busy, exportText, showExport }
|
|
182
184
|
loading: false,
|
|
183
185
|
error: "",
|
|
184
186
|
cat: "all",
|
|
@@ -186,6 +188,7 @@ window.__ModuleLoader__.load({
|
|
|
186
188
|
q: "",
|
|
187
189
|
detail: null,
|
|
188
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=自适应)
|
|
189
192
|
toggling: false, // v1.7.0: toggle 请求进行中(防重复点击)
|
|
190
193
|
toastTimer: null, // v1.7.1: toast 自动消失定时器句柄
|
|
191
194
|
batchMenuScope: null, // v1.7.2: 批量确认菜单当前作用域("global" 或分类 id)
|
|
@@ -196,12 +199,22 @@ window.__ModuleLoader__.load({
|
|
|
196
199
|
group: "all", // "all" | "ungrouped" | <groupId>
|
|
197
200
|
groupsLoaded: false,
|
|
198
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; } })(),
|
|
199
211
|
moveMenuDir: null, // 当前打开"移动到分组"菜单的技能目录
|
|
200
212
|
// ── v1.9.3 两级分组(大类 → 子类)──
|
|
201
213
|
gplan: null,
|
|
202
214
|
gplanMode: "", // AI 产出的待确认方案 {domains:[{name,emoji,subs:[{name,emoji,skills:[]}]}]}
|
|
203
215
|
gplanBusy: false, // 方案确认弹层是否已打开
|
|
204
216
|
gsub: "", // 当前选中大类下再选中的子类 id(""=看整个大类)
|
|
217
|
+
gFold: false, // v2.0.12 分组树子类行:true=全部收起 / false=全部展开
|
|
205
218
|
gmode: "tree", // v1.9.8 分组栏显示模式:"tree" 树状(大类一行+常驻子类)| "flat" 平铺(原样)
|
|
206
219
|
modelInfo: null, // v1.9.9 AI 模型探针结果 {ok,provider,model,source,error};null=还没探
|
|
207
220
|
newParent: "", // "新建分组"时选定的上级大类 id(""=建大类)
|
|
@@ -470,9 +483,11 @@ window.__ModuleLoader__.load({
|
|
|
470
483
|
if (S.group !== "all") {
|
|
471
484
|
var gid = S.gassign[s.dir] || "";
|
|
472
485
|
if (S.group === "ungrouped") { if (gid) continue; }
|
|
473
|
-
else if (S.gsub) { if (gid !== S.gsub) continue; }
|
|
474
486
|
else if (!inDomain(S.group, gid)) continue;
|
|
475
487
|
}
|
|
488
|
+
// v2.0.8 修复:子类过滤独立生效。树状模式直接点子类时 S.group 仍是 all,
|
|
489
|
+
// 原来 gsub 判断被 S.group!=="all" 挡住 → 点了子类列表不变(平铺因先选大类才正常)。
|
|
490
|
+
if (S.gsub && (S.gassign[s.dir] || "") !== S.gsub) continue;
|
|
476
491
|
if (q) {
|
|
477
492
|
var hay = (s.dir + " " + s.name + " " + (s.zh || "") + " " + (s.desc || "")).toLowerCase();
|
|
478
493
|
if (hay.indexOf(q) < 0) continue;
|
|
@@ -624,19 +639,47 @@ window.__ModuleLoader__.load({
|
|
|
624
639
|
.catch(function (e) { if (cb) cb({ ok: false, error: String((e && e.message) || e) }); });
|
|
625
640
|
}
|
|
626
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
|
+
}
|
|
627
671
|
function createGroupFlow() {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
}
|
|
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
|
+
});
|
|
640
683
|
});
|
|
641
684
|
}
|
|
642
685
|
|
|
@@ -644,36 +687,34 @@ window.__ModuleLoader__.load({
|
|
|
644
687
|
function createSubGroupFlow(parentId) {
|
|
645
688
|
var par = groupById(parentId);
|
|
646
689
|
if (!par) return;
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
}
|
|
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
|
+
});
|
|
659
701
|
});
|
|
660
702
|
}
|
|
661
703
|
|
|
662
704
|
function renameGroupFlow(id) {
|
|
663
705
|
var g = groupById(id);
|
|
664
706
|
if (!g) return;
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
}
|
|
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
|
+
});
|
|
677
718
|
});
|
|
678
719
|
}
|
|
679
720
|
|
|
@@ -694,17 +735,59 @@ window.__ModuleLoader__.load({
|
|
|
694
735
|
});
|
|
695
736
|
}
|
|
696
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
|
+
|
|
697
775
|
// v1.9.7: 关键词快速分组 —— 纯本地正则,零 token、毫秒级、结果确定。
|
|
698
776
|
// 与 AI 分组共用同一个方案弹层与 applyPlan 落盘路径。
|
|
699
777
|
function kwClassifyFlow() {
|
|
700
778
|
if (S.groupBusy) return;
|
|
701
779
|
S.groupBusy = "kw-classify";
|
|
780
|
+
S.aiStartTs = Date.now(); S.aiElapsedMs = 0; S.aiCached = false;
|
|
702
781
|
paintGroups();
|
|
703
782
|
postGroups({ action: "kw-classify" }, function (d) {
|
|
704
783
|
S.groupBusy = "";
|
|
784
|
+
S.aiElapsedMs = Date.now() - S.aiStartTs;
|
|
705
785
|
if (d && d.ok) {
|
|
706
786
|
S.gplan = d.plan || null;
|
|
707
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) { }
|
|
708
791
|
paintGroups();
|
|
709
792
|
openPlanMenu();
|
|
710
793
|
} else {
|
|
@@ -718,13 +801,21 @@ window.__ModuleLoader__.load({
|
|
|
718
801
|
function aiClassifyFlow() {
|
|
719
802
|
if (S.groupBusy) return;
|
|
720
803
|
S.groupBusy = "ai-classify";
|
|
804
|
+
S.aiStartTs = Date.now(); S.aiElapsedMs = 0; S.aiCached = false;
|
|
721
805
|
paintGroups();
|
|
722
806
|
showToast("ok", T("groupAiRunning"));
|
|
807
|
+
startAiTimer();
|
|
723
808
|
postGroups({ action: "ai-classify" }, function (d) {
|
|
809
|
+
stopAiTimer();
|
|
724
810
|
S.groupBusy = "";
|
|
811
|
+
S.aiElapsedMs = Date.now() - S.aiStartTs;
|
|
812
|
+
S.aiCached = !!(d && d.cached);
|
|
725
813
|
if (d && d.ok) {
|
|
726
814
|
S.gplan = d.plan || null;
|
|
727
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) { }
|
|
728
819
|
paintGroups();
|
|
729
820
|
openPlanMenu();
|
|
730
821
|
} else {
|
|
@@ -760,11 +851,21 @@ window.__ModuleLoader__.load({
|
|
|
760
851
|
"</div>";
|
|
761
852
|
}
|
|
762
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
|
+
}
|
|
763
862
|
var html = '<div class="dsb-bmenu-title">' + esc(planTitle) + "</div>" +
|
|
764
863
|
'<div class="dsb-plan-stat">' + esc(TF("groupPlanStat", { d: domN, s: subN, n: total })) + "</div>" +
|
|
864
|
+
timeLine +
|
|
765
865
|
'<div class="dsb-plan-list">' + rows + "</div>" +
|
|
766
866
|
'<button class="dsb-bmenu-item dsb-bmenu-danger" data-plan-act="replace"><span>' + esc(T("groupPlanReplace")) + "</span></button>" +
|
|
767
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>' +
|
|
768
869
|
'<button class="dsb-bmenu-cancel" data-plan-act="__cancel">' + esc(T("aggCancelText")) + "</button>";
|
|
769
870
|
menu.className = "dsb-bmenu dsb-plan-menu";
|
|
770
871
|
menu.innerHTML = html;
|
|
@@ -941,6 +1042,776 @@ window.__ModuleLoader__.load({
|
|
|
941
1042
|
});
|
|
942
1043
|
}
|
|
943
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
|
+
|
|
944
1815
|
// 本地日期工具(避免时区漂移:全部用本地 Date 取 Y-M-D)
|
|
945
1816
|
function ymd(d) {
|
|
946
1817
|
var p = function (n) { return n < 10 ? "0" + n : String(n); };
|
|
@@ -1062,7 +1933,7 @@ window.__ModuleLoader__.load({
|
|
|
1062
1933
|
if (!S.ledgerDetail || S.ledgerDetail.skill !== skill) return;
|
|
1063
1934
|
var parts = [];
|
|
1064
1935
|
if (d && d.ok) {
|
|
1065
|
-
if (d.detail) parts.push("
|
|
1936
|
+
if (d.detail) parts.push("【失败明细】\n" + d.detail);
|
|
1066
1937
|
if (d.autoEntries && d.autoEntries.length) {
|
|
1067
1938
|
var tot = (d.autoTotal == null ? d.autoEntries.length : d.autoTotal);
|
|
1068
1939
|
var shownN = d.autoEntries.length;
|
|
@@ -1162,7 +2033,7 @@ window.__ModuleLoader__.load({
|
|
|
1162
2033
|
'.dsb-cards{flex:1;min-height:0;overflow-y:auto;display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));' +
|
|
1163
2034
|
'gap:8px;padding:6px 16px 14px;align-content:start}' +
|
|
1164
2035
|
'.dsb-card{background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.08);border-radius:10px;' +
|
|
1165
|
-
'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}' +
|
|
1166
2037
|
'.dsb-card:hover{background:rgba(120,140,255,.14);border-color:rgba(120,140,255,.4)}' +
|
|
1167
2038
|
'.dsb-card-name{font-family:ui-monospace,Consolas,monospace;font-size:11px;color:#8fc7ff;word-break:break-all}' +
|
|
1168
2039
|
'.dsb-card-zh{font-size:11.5px;color:#dfe3ee;margin-top:5px;line-height:1.5;' +
|
|
@@ -1210,7 +2081,79 @@ window.__ModuleLoader__.load({
|
|
|
1210
2081
|
'.dsb-lst-warn{color:#ffb35c}' +
|
|
1211
2082
|
'.dsb-lst-ok{color:#9aa1b5}' +
|
|
1212
2083
|
/* ── v1.7.0:排序切换 / 批量启停 / 单技能开关 / 停用视觉 ── */
|
|
1213
|
-
'.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}' +
|
|
1214
2157
|
'.dsb-sortbtn{background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);color:#cdd3e3;' +
|
|
1215
2158
|
'border-radius:999px;padding:3px 12px;font-size:12px;cursor:pointer;white-space:nowrap}' +
|
|
1216
2159
|
'.dsb-sortbtn:hover{background:rgba(255,255,255,.14)}' +
|
|
@@ -1284,6 +2227,12 @@ window.__ModuleLoader__.load({
|
|
|
1284
2227
|
'#dsh-skill-browser-panel .dsb-bmenu-danger .dsb-bmenu-sub{color:#ff9d9d}' +
|
|
1285
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}' +
|
|
1286
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)}' +
|
|
1287
2236
|
'#dsh-skill-browser-panel .dsb-lc-date{font-size:10.5px;color:#8b93a8;white-space:nowrap}' +
|
|
1288
2237
|
'#dsh-skill-browser-panel .dsb-lc-rank{font-size:10.5px;color:#8b93a8;text-align:right;white-space:nowrap}' +
|
|
1289
2238
|
'#dsh-skill-browser-panel .dsb-lc-status{font-size:10.5px;color:#ffb3b3;white-space:nowrap}' +
|
|
@@ -1373,6 +2322,11 @@ window.__ModuleLoader__.load({
|
|
|
1373
2322
|
'#dsh-skill-browser-panel .dsb-gadd-kw{border-color:var(--dsb-okline,rgba(80,220,150,.34));color:var(--dsb-ok,#9ff5c8)}' +
|
|
1374
2323
|
'#dsh-skill-browser-panel .dsb-gbusy{opacity:.55;pointer-events:none}' +
|
|
1375
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}' +
|
|
1376
2330
|
'#dsh-skill-browser-panel .dsb-ghint{font-size:10.5px;color:var(--dsb-dim,#6f778c);white-space:nowrap;' +
|
|
1377
2331
|
'overflow:hidden;text-overflow:ellipsis;max-width:44%}' +
|
|
1378
2332
|
// v1.9.9 对比度修正:数字徽标一律「继承父级文字色」,不再写死
|
|
@@ -1412,6 +2366,30 @@ window.__ModuleLoader__.load({
|
|
|
1412
2366
|
/* ── v1.9.3:AI 分组方案确认弹层 ── */
|
|
1413
2367
|
'#dsh-skill-browser-panel .dsb-plan-menu{min-width:0;width:calc(100% - 24px);left:12px;overflow-y:auto}' +
|
|
1414
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}' +
|
|
1415
2393
|
'#dsh-skill-browser-panel .dsb-plan-list{max-height:52vh;overflow-y:auto;padding:2px 4px 6px;display:flex;' +
|
|
1416
2394
|
'flex-direction:column;gap:5px}' +
|
|
1417
2395
|
'#dsh-skill-browser-panel .dsb-plan-dom{background:var(--dsb-soft,rgba(255,255,255,.04));' +
|
|
@@ -1429,7 +2407,7 @@ window.__ModuleLoader__.load({
|
|
|
1429
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);' +
|
|
1430
2408
|
'border-radius:6px;font-size:10.5px;padding:2px 7px;cursor:pointer;white-space:nowrap}' +
|
|
1431
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)}' +
|
|
1432
|
-
'#dsh-skill-browser-panel .dsb-card-foot{display:flex;align-items:center;justify-content:space-between;gap:6px;margin-top:
|
|
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}' +
|
|
1433
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}' +
|
|
1434
2412
|
/* ── v1.9.1:调用台账看板(全部改用语义变量,浅色皮肤同样可读)── */
|
|
1435
2413
|
'#dsh-skill-browser-panel .dsb-lbar{display:flex;flex-wrap:wrap;align-items:center;gap:6px;padding:0 0 8px}' +
|
|
@@ -1499,11 +2477,12 @@ window.__ModuleLoader__.load({
|
|
|
1499
2477
|
panel.id = PANEL_ID;
|
|
1500
2478
|
panel.innerHTML =
|
|
1501
2479
|
'<div class="dsb-head">' +
|
|
1502
|
-
'<div class="dsb-title"
|
|
2480
|
+
'<div class="dsb-title">技能与 MCP 浏览器</div>' +
|
|
1503
2481
|
'<div class="dsb-tabs">' +
|
|
1504
2482
|
'<button id="dsb-tab-skills" class="dsb-tab" data-view="skills">技能浏览</button>' +
|
|
1505
2483
|
'<button id="dsb-tab-ledger" class="dsb-tab" data-view="ledger">调用台账</button>' +
|
|
1506
|
-
'<button id="dsb-tab-
|
|
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>' +
|
|
1507
2486
|
'</div>' +
|
|
1508
2487
|
'<div class="dsb-headbtns">' +
|
|
1509
2488
|
'<button id="dsb-skin-btn" class="dsb-btn" title="切换皮肤(三球同步 · 130款)">' + DSB_ICONS.palette + '</button>' +
|
|
@@ -1517,7 +2496,7 @@ window.__ModuleLoader__.load({
|
|
|
1517
2496
|
'<div id="dsb-skin" class="dsb-skinmenu"></div>' +
|
|
1518
2497
|
'<div id="dsb-subtitle" class="dsb-subtitle"></div>' +
|
|
1519
2498
|
'<div id="dsb-settings" class="dsb-settings" style="display:none">' +
|
|
1520
|
-
'<label class="dsb-set-row"><input type="checkbox" id="dsb-set-ball" />
|
|
2499
|
+
'<label class="dsb-set-row"><input type="checkbox" id="dsb-set-ball" /> 技能与 MCP 浏览器悬浮球(🧩)</label>' +
|
|
1521
2500
|
'<label class="dsb-set-row"><input type="checkbox" id="dsb-set-font" /> 字体插件悬浮球(🔤)</label>' +
|
|
1522
2501
|
'<div class="dsb-set-row" style="flex-wrap:wrap">' +
|
|
1523
2502
|
'<span style="min-width:70px">技能库位置</span>' +
|
|
@@ -1525,12 +2504,11 @@ window.__ModuleLoader__.load({
|
|
|
1525
2504
|
'<button id="dsb-set-root-browse" class="dsb-btn" title="打开系统文件夹选择框">📁 选择</button>' +
|
|
1526
2505
|
'<button id="dsb-set-root-save" class="dsb-btn">保存</button>' +
|
|
1527
2506
|
'</div>' +
|
|
1528
|
-
'<div class="dsb-set-row" style="flex-wrap:wrap">' +
|
|
1529
|
-
'<span style="min-width:70px">悬浮球皮肤</span>' +
|
|
1530
|
-
'<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>' +
|
|
1531
|
-
'<span class="dsb-set-hint">三球同步 · 128 款</span>' +
|
|
1532
|
-
'</div>' +
|
|
1533
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>' +
|
|
1534
2512
|
'<span id="dsb-set-status" class="dsb-set-hint">当前技能库:识别中…</span>' +
|
|
1535
2513
|
'<span class="dsb-set-hint">💡 悬浮球显隐也可在 <b>DSH 设置页 → 「悬浮球导航」分区</b> 中控制;悬浮球可拖动;清单面板始终居中</span>' +
|
|
1536
2514
|
// ── v1.7.4 广告区(用户指定落位:设置面板 hint 之后)──
|
|
@@ -1560,10 +2538,11 @@ window.__ModuleLoader__.load({
|
|
|
1560
2538
|
'<div id="dsb-chips" class="dsb-chips"></div>' +
|
|
1561
2539
|
'<div id="dsb-subs" class="dsb-subs"></div>' +
|
|
1562
2540
|
'<div id="dsb-cards" class="dsb-cards"></div>' +
|
|
2541
|
+
'<div id="dsb-mcp" class="dsb-mcp" style="display:none"></div>' +
|
|
1563
2542
|
'<div id="dsb-guide" class="dsb-guide" style="display:none">' +
|
|
1564
2543
|
'<div class="dsb-guide-inner">' +
|
|
1565
2544
|
'<h3>' + DSB_ICONS.book + '快速上手</h3>' +
|
|
1566
|
-
'<p class="dsb-guide-sub">60
|
|
2545
|
+
'<p class="dsb-guide-sub">60 秒学会用技能与 MCP 浏览器管理你的技能库</p>' +
|
|
1567
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>' +
|
|
1568
2547
|
'<div class="dsb-guide-card">' + dsbGico('refresh') + '<b>② 刷新,让新技能进列表</b><br>技能文件增删改之后,点右上角 <b>刷新</b>(↻)重新扫描技能库。要让它<b>不重启 DSH 立即生效</b>,再点一下 <b>热重载</b>(⚡)。顺序永远是:<b>先选库 → 再刷新 → 最后热重载</b>。</div>' +
|
|
1569
2548
|
'<div class="dsb-guide-card">' + dsbGico('search') + '<b>③ 搜索与筛选技能</b><br>顶部搜索框支持<b>技能名 / 中文简介 / 描述</b>关键字。下方分组栏:<b>我的分组</b>(树状/平铺切换)→ <b>关键词分组</b>(按触发词自动归类)→ <b>AI 自动分组</b>(调模型智能归类)。存在自定义分组时,默认分类行会自动隐藏。</div>' +
|
|
@@ -1641,6 +2620,10 @@ window.__ModuleLoader__.load({
|
|
|
1641
2620
|
var pact = pa.getAttribute("data-plan-act");
|
|
1642
2621
|
if (pact === "replace") applyPlanFlow("replace");
|
|
1643
2622
|
else if (pact === "merge") applyPlanFlow("merge");
|
|
2623
|
+
else if (pact === "recache") {
|
|
2624
|
+
closePlanMenu();
|
|
2625
|
+
postGroups({ action: "clear-classify-cache" }, function () { aiClassifyFlow(); });
|
|
2626
|
+
}
|
|
1644
2627
|
else closePlanMenu();
|
|
1645
2628
|
return;
|
|
1646
2629
|
}
|
|
@@ -1682,6 +2665,12 @@ window.__ModuleLoader__.load({
|
|
|
1682
2665
|
paintGroups();
|
|
1683
2666
|
return;
|
|
1684
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
|
+
}
|
|
1685
2674
|
var add = ev.target.closest("[data-gadd]");
|
|
1686
2675
|
if (add) {
|
|
1687
2676
|
var how = add.getAttribute("data-gadd");
|
|
@@ -1694,19 +2683,23 @@ window.__ModuleLoader__.load({
|
|
|
1694
2683
|
var gs = ev.target.closest("[data-gsub]");
|
|
1695
2684
|
if (gs) {
|
|
1696
2685
|
S.gsub = gs.getAttribute("data-gsub") || "";
|
|
1697
|
-
paintGroups(); paintCards();
|
|
2686
|
+
paintGroups(); paintCards(); paintSortbar();
|
|
1698
2687
|
return;
|
|
1699
2688
|
}
|
|
1700
2689
|
var card = ev.target.closest("[data-gsel]");
|
|
1701
2690
|
if (card) {
|
|
1702
2691
|
S.group = card.getAttribute("data-gsel") || "all";
|
|
1703
2692
|
S.gsub = "";
|
|
1704
|
-
paintGroups(); paintCards();
|
|
2693
|
+
paintGroups(); paintCards(); paintSortbar();
|
|
1705
2694
|
}
|
|
1706
2695
|
});
|
|
1707
2696
|
|
|
1708
2697
|
$("dsb-close").addEventListener("click", function () { closePanel(); });
|
|
1709
|
-
$("dsb-refresh").addEventListener("click", function () {
|
|
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
|
+
});
|
|
1710
2703
|
|
|
1711
2704
|
// ── v1.9.4 热加载:重新加载插件代码,不重启桌面端 ──────────────
|
|
1712
2705
|
$("dsb-hot").addEventListener("click", function () { hotReloadFlow(); });
|
|
@@ -1738,7 +2731,23 @@ window.__ModuleLoader__.load({
|
|
|
1738
2731
|
});
|
|
1739
2732
|
var rootBrowse = $("dsb-set-root-browse");
|
|
1740
2733
|
if (rootBrowse) rootBrowse.addEventListener("click", function () { browseForDirectory(); });
|
|
1741
|
-
|
|
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(); });
|
|
1742
2751
|
// v1.9.6 技能列表滚到底自动追加一批(无感分页,避免一次性渲染上千节点)
|
|
1743
2752
|
$("dsb-cards").addEventListener("scroll", function () {
|
|
1744
2753
|
var b = this;
|
|
@@ -1757,6 +2766,18 @@ window.__ModuleLoader__.load({
|
|
|
1757
2766
|
if (!S.lstat) loadStats();
|
|
1758
2767
|
});
|
|
1759
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
|
+
}
|
|
1760
2781
|
$("dsb-ledger-table").addEventListener("click", function (ev) {
|
|
1761
2782
|
// 排行表头排序
|
|
1762
2783
|
var sEl = ev.target.closest("[data-lsort]");
|
|
@@ -1801,7 +2822,7 @@ window.__ModuleLoader__.load({
|
|
|
1801
2822
|
var el = ev.target.closest("[data-sub]");
|
|
1802
2823
|
if (!el) return;
|
|
1803
2824
|
S.sub = el.getAttribute("data-sub");
|
|
1804
|
-
paintSubs(); paintCards();
|
|
2825
|
+
paintSubs(); paintCards(); paintSortbar();
|
|
1805
2826
|
});
|
|
1806
2827
|
// v1.7.0 排序切换 + v1.7.1 四态循环 + 分类级批量启停(事件委托到排序栏容器)
|
|
1807
2828
|
$("dsb-sortbar").addEventListener("click", function (ev) {
|
|
@@ -1819,6 +2840,13 @@ window.__ModuleLoader__.load({
|
|
|
1819
2840
|
openBatchMenu(agg, agg.getAttribute("data-bagg") || "global");
|
|
1820
2841
|
return;
|
|
1821
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
|
+
}
|
|
1822
2850
|
});
|
|
1823
2851
|
// v1.7.4 设置面板推广卡(data-ext-url)点击直达 GitHub:
|
|
1824
2852
|
// 广告卡迁入 #dsb-settings 后,原 #dsb-cards 委托覆盖不到,此处补一条同款委托。
|
|
@@ -1912,8 +2940,9 @@ window.__ModuleLoader__.load({
|
|
|
1912
2940
|
if (!S.open) return;
|
|
1913
2941
|
var isLedger = S.view === "ledger";
|
|
1914
2942
|
var isGuide = S.view === "guide";
|
|
1915
|
-
var
|
|
1916
|
-
var
|
|
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";
|
|
1917
2946
|
if (q) q.style.display = skillsVis;
|
|
1918
2947
|
if (groups) groups.style.display = skillsVis;
|
|
1919
2948
|
if (chips) chips.style.display = skillsVis;
|
|
@@ -1922,12 +2951,15 @@ window.__ModuleLoader__.load({
|
|
|
1922
2951
|
if (sortbar) sortbar.style.display = skillsVis;
|
|
1923
2952
|
if (lg) lg.style.display = isLedger ? "flex" : "none";
|
|
1924
2953
|
if (gd) gd.style.display = isGuide ? "block" : "none";
|
|
1925
|
-
|
|
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");
|
|
1926
2956
|
if (t1) t1.classList.toggle("dsb-tab-on", S.view === "skills");
|
|
1927
2957
|
if (t2) t2.classList.toggle("dsb-tab-on", isLedger);
|
|
1928
2958
|
if (t3) t3.classList.toggle("dsb-tab-on", isGuide);
|
|
2959
|
+
if (tm) tm.classList.toggle("dsb-tab-on", isMcp);
|
|
1929
2960
|
paintHeader();
|
|
1930
2961
|
if (isLedger) paintLedger();
|
|
2962
|
+
else if (isMcp) paintMcp();
|
|
1931
2963
|
else if (!isGuide) { paintGroups(); paintSortbar(); paintChips(); paintSubs(); paintCards(); }
|
|
1932
2964
|
paintDetail();
|
|
1933
2965
|
}
|
|
@@ -1953,14 +2985,19 @@ window.__ModuleLoader__.load({
|
|
|
1953
2985
|
var bar = '<div class="dsb-gbar">';
|
|
1954
2986
|
bar += '<span class="dsb-gtitle" title="' + esc(T("groupsHint")) + '">' + esc(T("groupsTitle")) + "</span>";
|
|
1955
2987
|
bar += '<button class="dsb-gadd' + (S.group === "all" ? " dsb-gadd-on" : "") + '" data-gsel="all">' +
|
|
1956
|
-
esc(T("groupAll")) + ' <span class="dsb-gn">' + total + "
|
|
2988
|
+
esc(T("groupAll")) + ' <span class="dsb-gn">' + total + " 项</span></button>";
|
|
1957
2989
|
bar += '<button class="dsb-gadd' + (S.group === "ungrouped" ? " dsb-gadd-on" : "") + '" data-gsel="ungrouped">' +
|
|
1958
|
-
esc(T("groupUngrouped")) + ' <span class="dsb-gn">' + ungrouped + "
|
|
1959
|
-
bar += '<span class="dsb-
|
|
2990
|
+
esc(T("groupUngrouped")) + ' <span class="dsb-gn">' + ungrouped + " 项</span></button>";
|
|
2991
|
+
bar += '<span class="dsb-gsep"></span>';
|
|
1960
2992
|
bar += '<button class="dsb-gadd' + (treeMode ? " dsb-gadd-on" : "") + '" data-gmode="tree" title="' +
|
|
1961
2993
|
esc(T("groupModeTreeTip")) + '">' + esc(T("groupModeTree")) + "</button>";
|
|
1962
2994
|
bar += '<button class="dsb-gadd' + (treeMode ? "" : " dsb-gadd-on") + '" data-gmode="flat" title="' +
|
|
1963
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
|
+
}
|
|
1964
3001
|
bar += '<button class="dsb-gadd dsb-gadd-kw' + (S.groupBusy === "kw-classify" ? " dsb-gbusy" : "") +
|
|
1965
3002
|
'" data-gadd="kw" title="' + esc(T("groupKwTip")) + '">' +
|
|
1966
3003
|
esc(S.groupBusy === "kw-classify" ? T("processingText") : T("groupKw")) + "</button>";
|
|
@@ -1976,16 +3013,22 @@ window.__ModuleLoader__.load({
|
|
|
1976
3013
|
miCls = " dsb-gmodel-idle";
|
|
1977
3014
|
miTip = T("modelProbeTip");
|
|
1978
3015
|
} else if (mi.ok) {
|
|
1979
|
-
|
|
1980
|
-
|
|
3016
|
+
// v2.0.9 用户要求:只显示「跟随当前对话模型」,具体模型放 title 悬浮可见。
|
|
3017
|
+
miText = "🤖 跟随当前对话模型 · 正常";
|
|
1981
3018
|
miCls = " dsb-gmodel-ok";
|
|
1982
|
-
miTip = (mi.provider || "") + " / " + (mi.model || "") + "(" + (mi.source || "") + ")";
|
|
3019
|
+
miTip = "跟随当前对话模型 · 实际:" + (mi.provider || "") + " / " + (mi.model || "") + "(" + (mi.source || "") + ")";
|
|
1983
3020
|
} else {
|
|
1984
3021
|
miText = "⚠ " + esc(T("modelNone"));
|
|
1985
3022
|
miCls = " dsb-gmodel-bad";
|
|
1986
3023
|
miTip = mi.error || T("modelNone");
|
|
1987
3024
|
}
|
|
1988
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
|
+
}
|
|
1989
3032
|
bar += '<span class="dsb-gmodel' + miCls + '" data-gmodel="1" title="' + esc(miTip) + '">' + miText + "</span>";
|
|
1990
3033
|
bar += "</div>";
|
|
1991
3034
|
|
|
@@ -2016,9 +3059,8 @@ window.__ModuleLoader__.load({
|
|
|
2016
3059
|
"</div>";
|
|
2017
3060
|
// 子类行常驻:选中该大类时把「整个大类」作为第一个可选项。
|
|
2018
3061
|
// 用 ├─ / └─ 连接符做成真正的树枝,每行一个子类,层层展开。
|
|
2019
|
-
if (kids.length
|
|
3062
|
+
if (kids.length && !S.gFold) {
|
|
2020
3063
|
var rows = [];
|
|
2021
|
-
if (on) rows.push({ id: "", label: T("groupWhole"), emoji: "", n: n, tip: T("groupWhole") });
|
|
2022
3064
|
for (var k = 0; k < kids.length; k++) {
|
|
2023
3065
|
var sg = kids[k];
|
|
2024
3066
|
rows.push({
|
|
@@ -2039,7 +3081,7 @@ window.__ModuleLoader__.load({
|
|
|
2039
3081
|
'<button class="dsb-gsub' + (sOn ? " dsb-gsub-on" : "") +
|
|
2040
3082
|
'" data-gsub="' + esc(it.id) + '" title="' + esc(it.tip) + '">' +
|
|
2041
3083
|
(it.emoji ? esc(it.emoji) + " " : "") + esc(it.label) +
|
|
2042
|
-
' <span class="dsb-gn">' + it.n + "
|
|
3084
|
+
' <span class="dsb-gn">' + it.n + " 项</span>" +
|
|
2043
3085
|
"</button>" +
|
|
2044
3086
|
"</div>";
|
|
2045
3087
|
}
|
|
@@ -2083,14 +3125,12 @@ window.__ModuleLoader__.load({
|
|
|
2083
3125
|
var kids2 = subsOf(cur.id);
|
|
2084
3126
|
if (kids2.length) {
|
|
2085
3127
|
body += '<div class="dsb-gsubs"><span class="dsb-gtitle">└ ' + esc(T("groupSubsOf")) + "</span>";
|
|
2086
|
-
body += '<button class="dsb-gadd' + (S.gsub ? "" : " dsb-gadd-on") + '" data-gsub="">' +
|
|
2087
|
-
esc(T("groupWhole")) + ' <span class="dsb-gn">' + domainCount(cur) + "</span></button>";
|
|
2088
3128
|
for (var q = 0; q < kids2.length; q++) {
|
|
2089
3129
|
var sg2 = kids2[q];
|
|
2090
3130
|
var sn2 = (sg2.members || []).length;
|
|
2091
3131
|
body += '<button class="dsb-gadd' + (S.gsub === sg2.id ? " dsb-gadd-on" : "") +
|
|
2092
3132
|
'" data-gsub="' + esc(sg2.id) + '" title="' + esc(sg2.name + " · " + sn2 + " 项") + '">' +
|
|
2093
|
-
esc((sg2.emoji || "📁") + " " + sg2.name) + ' <span class="dsb-gn">' + sn2 + "
|
|
3133
|
+
esc((sg2.emoji || "📁") + " " + sg2.name) + ' <span class="dsb-gn">' + sn2 + " 项</span></button>";
|
|
2094
3134
|
}
|
|
2095
3135
|
body += "</div>";
|
|
2096
3136
|
}
|
|
@@ -2217,25 +3257,22 @@ window.__ModuleLoader__.load({
|
|
|
2217
3257
|
// v1.7.2 全局总开关(最重要):标签"全部技能",状态=所有技能聚合
|
|
2218
3258
|
// (全启用→右绿 / 全停用→左灰 / 混合→居中半开+title"部分停用");
|
|
2219
3259
|
// 点击任意态弹面板内确认菜单 openBatchMenu
|
|
2220
|
-
|
|
2221
|
-
var
|
|
3260
|
+
// v2.0.7 聚合开关作用于「当前筛选/分组显示的列表」(filtered),并显示数量
|
|
3261
|
+
var fl = filtered();
|
|
3262
|
+
var ga = aggCount(fl, null);
|
|
2222
3263
|
var gs = aggState(ga);
|
|
2223
|
-
var gTitle = gs === "half" ? TF("aggMixedTitle", { x: ga.off, y: ga.total })
|
|
2224
|
-
: (gs === "on" ? T("aggAllOnTitle") : T("aggAllOffTitle"));
|
|
2225
|
-
var
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
if (S.cat !== "all") {
|
|
2229
|
-
var ca = aggCount(sk, S.cat);
|
|
2230
|
-
var cs = aggState(ca);
|
|
2231
|
-
var cTitle = catLabel(S.cat) + " · " + (cs === "half" ? TF("aggMixedTitle", { x: ca.off, y: ca.total })
|
|
2232
|
-
: (cs === "on" ? T("aggAllOnTitle") : T("aggAllOffTitle")));
|
|
2233
|
-
html += '<span class="dsb-sw-sep"></span>' +
|
|
2234
|
-
aggSwitchHtml(cs, 'data-bagg="' + esc(S.cat) + '"', cTitle, "", true) +
|
|
2235
|
-
'<span class="dsb-batch-status">' + esc(TF("aggCatStat", { x: ca.off, y: ca.total })) + '</span>';
|
|
2236
|
-
}
|
|
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);
|
|
2237
3269
|
// v1.7.1 保留:批量进行中显示"处理中…"(滑块同时 aria-disabled 防重入)
|
|
2238
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
|
+
});
|
|
2239
3276
|
box.innerHTML = html;
|
|
2240
3277
|
}
|
|
2241
3278
|
|
|
@@ -2388,16 +3425,20 @@ window.__ModuleLoader__.load({
|
|
|
2388
3425
|
// ③paintSortbar 重算全局+分类聚合滑块与"停用 x/y"小字;④paintHeader 刷副标题。
|
|
2389
3426
|
function batchToggle(scope, disable) {
|
|
2390
3427
|
if (S.toggling) return;
|
|
2391
|
-
var isGlobal = scope === "global";
|
|
2392
3428
|
var sk = (S.data && S.data.skills) || [];
|
|
2393
3429
|
var dirs = [];
|
|
2394
|
-
|
|
2395
|
-
|
|
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
|
+
}
|
|
2396
3437
|
}
|
|
2397
3438
|
if (!dirs.length) return;
|
|
2398
3439
|
S.toggling = true;
|
|
2399
3440
|
paintSortbar(); // 聚合滑块进入"处理中…"半透明态(aria-disabled 防重入)
|
|
2400
|
-
var body =
|
|
3441
|
+
var body = { dirs: dirs, disable: disable };
|
|
2401
3442
|
fetch(apiBase() + "/toggle-batch", {
|
|
2402
3443
|
method: "POST",
|
|
2403
3444
|
headers: { "content-type": "application/json" },
|
|
@@ -2426,7 +3467,7 @@ window.__ModuleLoader__.load({
|
|
|
2426
3467
|
} else {
|
|
2427
3468
|
showToast("ok", TF(disable ? "toastBatchDisabled" : "toastBatchEnabled", { n: okN }));
|
|
2428
3469
|
}
|
|
2429
|
-
ctxLogger("batch " + (
|
|
3470
|
+
ctxLogger("batch " + (scope === "global" ? "global" : scope) + ": ok=" + okN + " fail=" + fails);
|
|
2430
3471
|
} else {
|
|
2431
3472
|
showToast("err", TF("toastToggleFail", { err: (d && d.error) || "unknown" }));
|
|
2432
3473
|
try { console.warn("[dsh-skill-browser] batch failed: " + ((d && d.error) || "unknown")); } catch (e) {}
|
|
@@ -2448,14 +3489,15 @@ window.__ModuleLoader__.load({
|
|
|
2448
3489
|
if (S.toggling) return;
|
|
2449
3490
|
var menu = $("dsb-bmenu"), mask = $("dsb-bmenu-mask"), panel = $(PANEL_ID);
|
|
2450
3491
|
if (!menu || !mask || !panel || !anchor) return;
|
|
3492
|
+
var isFiltered = scope === "filtered";
|
|
2451
3493
|
var isGlobal = scope === "global";
|
|
2452
|
-
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);
|
|
2453
3495
|
var n = a.total;
|
|
2454
|
-
var scopeName = isGlobal ? T("aggAllLabel") : catLabel(scope);
|
|
2455
|
-
var enSub = isGlobal ? "" : T("batchConfirmEnable");
|
|
2456
|
-
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");
|
|
2457
3499
|
menu.innerHTML =
|
|
2458
|
-
'<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/>' +
|
|
2459
3501
|
esc(TF("aggMenuStat", { scope: scopeName, n: n, x: a.off })) + '</div>' +
|
|
2460
3502
|
'<button class="dsb-bmenu-item" data-bmenu-act="enable"><span>' + esc(TF("aggEnableN", { n: n })) + '</span>' +
|
|
2461
3503
|
(enSub ? '<span class="dsb-bmenu-sub">' + esc(enSub) + '</span>' : "") + '</button>' +
|
|
@@ -2524,6 +3566,7 @@ window.__ModuleLoader__.load({
|
|
|
2524
3566
|
}
|
|
2525
3567
|
function paintCards() {
|
|
2526
3568
|
var box = $("dsb-cards"); if (!box) return;
|
|
3569
|
+
box.style.gridTemplateColumns = S.cardCols ? ("repeat(" + S.cardCols + ",minmax(0,1fr))") : "";
|
|
2527
3570
|
if (S.error && !S.data) {
|
|
2528
3571
|
box.innerHTML = '<div class="dsb-empty">' + esc(S.error) + '<br/><br/><button class="dsb-btn" id="dsb-retry">重试</button></div>';
|
|
2529
3572
|
var r = $("dsb-retry");
|
|
@@ -2567,7 +3610,7 @@ window.__ModuleLoader__.load({
|
|
|
2567
3610
|
'<div class="dsb-card-foot">' +
|
|
2568
3611
|
gTag +
|
|
2569
3612
|
'<button class="dsb-movebtn" data-move="' + esc(s.dir) + '" title="' + esc(T("groupAssignTip")) + '">' +
|
|
2570
|
-
"
|
|
3613
|
+
"🔀 " + esc(T("groupMove")) + "</button>" +
|
|
2571
3614
|
"</div>" +
|
|
2572
3615
|
'<div style="margin-top:6px">' +
|
|
2573
3616
|
'<label class="dsb-switch' + (off ? "" : " dsb-sw-on") + '" data-tgl="' + esc(s.dir) + '" data-to="' + (off ? "0" : "1") +
|
|
@@ -2739,43 +3782,16 @@ window.__ModuleLoader__.load({
|
|
|
2739
3782
|
}
|
|
2740
3783
|
rank.innerHTML = rh;
|
|
2741
3784
|
|
|
2742
|
-
// ---- 5)
|
|
3785
|
+
// ---- 5) 原始流水(只保留自动登记;人工台账区块按用户要求移除)----
|
|
2743
3786
|
var l = S.ledger || {};
|
|
2744
|
-
var rows = (l.entries || []).filter(function (e) { return e.fails > 0; });
|
|
2745
3787
|
var auto = l.autoRecent || [];
|
|
2746
3788
|
var flowOpen = !!S.lstatShowFlow;
|
|
2747
|
-
var fh = '<div class="dsb-lsec"><span>🗂 ' + esc(T("
|
|
2748
|
-
'<span class="sub"
|
|
3789
|
+
var fh = '<div class="dsb-lsec"><span>🗂 ' + esc(T("lstatAuto")) + "</span>" +
|
|
3790
|
+
'<span class="sub">' + esc(T("lstatAutoTip")) + " · " + auto.length + " 条</span>" +
|
|
2749
3791
|
'<button class="dsb-ldetail-toggle" data-lflow="1" style="margin-left:auto">' +
|
|
2750
3792
|
esc(flowOpen ? T("lstatExpand") : T("lstatCollapse")) + "</button></div>";
|
|
2751
3793
|
|
|
2752
3794
|
if (flowOpen) {
|
|
2753
|
-
fh += '<div class="dsb-ltable">' +
|
|
2754
|
-
'<div class="dsb-lrow dsb-lhead"><span class="dsb-lc-rank">#</span><span class="dsb-lc-skill">技能名</span>' +
|
|
2755
|
-
'<span class="dsb-lc-fail">失败</span><span class="dsb-lc-rate">等级</span>' +
|
|
2756
|
-
'<span class="dsb-lc-date">最近失败</span><span class="dsb-lc-status">强化状态</span></div>';
|
|
2757
|
-
if (!rows.length) {
|
|
2758
|
-
fh += '<div class="dsb-lrow"><span class="dsb-lc-skill" style="opacity:.7;line-height:1.8">' +
|
|
2759
|
-
"暂无人工登记。<br/>💡 技能触发失败时,在对话里说「<b>触发失败,登记</b>」或「<b>登记失败:技能名 原因</b>」,AI 会自动写入台账。</span></div>";
|
|
2760
|
-
}
|
|
2761
|
-
for (var i = 0; i < rows.length; i++) {
|
|
2762
|
-
var e = rows[i];
|
|
2763
|
-
var lv = ledgerLevel(e.fails);
|
|
2764
|
-
var status = e.status || "";
|
|
2765
|
-
var statusCls = /待强化|⚠/.test(status) ? "dsb-lst-warn" : /已强化/.test(status) ? "dsb-lst-done" : "dsb-lst-ok";
|
|
2766
|
-
fh += '<div class="dsb-lrow" data-lskill="' + esc(e.skill) + '" title="点击查看失败明细">' +
|
|
2767
|
-
'<span class="dsb-lc-rank">' + esc(e.rank || "—") + "</span>" +
|
|
2768
|
-
'<span class="dsb-lc-skill">' + esc(e.skill) + "</span>" +
|
|
2769
|
-
'<span class="dsb-lc-fail">' + e.fails + "</span>" +
|
|
2770
|
-
'<span class="dsb-lc-rate"><span class="' + lv.cls + '">' + lv.label + "</span></span>" +
|
|
2771
|
-
'<span class="dsb-lc-date">' + esc(e.lastDate || "—") + "</span>" +
|
|
2772
|
-
'<span class="dsb-lc-status ' + statusCls + '">' + esc(status) + "</span>" +
|
|
2773
|
-
"</div>";
|
|
2774
|
-
}
|
|
2775
|
-
fh += "</div>";
|
|
2776
|
-
|
|
2777
|
-
fh += '<div class="dsb-lsec"><span>🤖 ' + esc(T("lstatAuto")) + '</span><span class="sub">' +
|
|
2778
|
-
esc(T("lstatAutoTip")) + " · " + auto.length + " 条</span></div>";
|
|
2779
3795
|
if (!auto.length) {
|
|
2780
3796
|
fh += '<div class="dsb-empty" style="padding:12px 0">暂无自动记录 — 插件会记录每次技能工具调用结果(成功✅ / 失败❌)</div>';
|
|
2781
3797
|
} else {
|
|
@@ -2829,7 +3845,7 @@ window.__ModuleLoader__.load({
|
|
|
2829
3845
|
var ballCb = $("dsb-set-ball"), fontCb = $("dsb-set-font");
|
|
2830
3846
|
if (ballCb) ballCb.checked = readStore(BALL_VIS_KEY, true);
|
|
2831
3847
|
if (fontCb) fontCb.checked = readStore(FONT_BALL_VIS_KEY, true);
|
|
2832
|
-
try {
|
|
3848
|
+
try { } catch (eSkin) {}
|
|
2833
3849
|
}
|
|
2834
3850
|
|
|
2835
3851
|
// ---- 球体样式 + 字体球统一样式 + 显隐规则(纯 CSS,永久生效)----
|
|
@@ -3424,7 +4440,6 @@ window.__ModuleLoader__.load({
|
|
|
3424
4440
|
document.head.appendChild(el); // (re)append last so overrides win the cascade
|
|
3425
4441
|
ensureSkinMenuCss();
|
|
3426
4442
|
refreshSkinMenu();
|
|
3427
|
-
fillSkinSelect(document.getElementById("dsb-set-skin"));
|
|
3428
4443
|
fillSkinSelect(document.getElementById("dsh-set-skin-global"));
|
|
3429
4444
|
}
|
|
3430
4445
|
function skinOptionsHtml() {
|
|
@@ -3450,6 +4465,7 @@ window.__ModuleLoader__.load({
|
|
|
3450
4465
|
}
|
|
3451
4466
|
// ---- todo-style grouped skin menu (panel 🎨) ----
|
|
3452
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>',
|
|
3453
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>',
|
|
3454
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>',
|
|
3455
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>',
|
|
@@ -3545,12 +4561,6 @@ window.__ModuleLoader__.load({
|
|
|
3545
4561
|
document.addEventListener("change", function (ev) {
|
|
3546
4562
|
var t = ev.target;
|
|
3547
4563
|
if (!t || !t.id) return;
|
|
3548
|
-
if (t.id === "dsb-set-skin") {
|
|
3549
|
-
if (t.value === "classic") { setSkinClassic(true); return; }
|
|
3550
|
-
setSkinClassic(false);
|
|
3551
|
-
SKIN.setSkin(t.value);
|
|
3552
|
-
return;
|
|
3553
|
-
}
|
|
3554
4564
|
if (t.id === "dsh-set-skin-global") {
|
|
3555
4565
|
// 全局三球统一换肤:清掉本球与字体球的「原版」标记
|
|
3556
4566
|
try { localStorage.removeItem(SKIN_CLASSIC_KEY); } catch (e) {}
|
|
@@ -3595,7 +4605,7 @@ window.__ModuleLoader__.load({
|
|
|
3595
4605
|
React.createElement("span", null, label + (on ? "" : "(未安装)")));
|
|
3596
4606
|
}
|
|
3597
4607
|
rows.push(React.createElement("div", { key: "head", style: { fontWeight: "600", fontSize: "13px", marginBottom: "2px" } }, "悬浮球"));
|
|
3598
|
-
rows.push(mkToggle("dsb-set-ball", "
|
|
4608
|
+
rows.push(mkToggle("dsb-set-ball", "技能与 MCP 浏览器", function (e) {
|
|
3599
4609
|
writeStore(BALL_VIS_KEY, e.target.checked);
|
|
3600
4610
|
applyBallVisibility();
|
|
3601
4611
|
if (!e.target.checked && S.open) closePanel();
|