dsh-paoding 0.4.4 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/README_en.md +5 -4
- package/bin/dsh-paoding.mjs +22 -10
- package/docs/architecture.md +6 -5
- package/docs/architecture_en.md +8 -7
- package/docs/configuration.md +9 -1
- package/docs/configuration_en.md +9 -1
- package/docs/installation.md +17 -7
- package/docs/installation_en.md +14 -6
- package/docs/orchestration.md +2 -2
- package/docs/orchestration_en.md +2 -2
- package/package.json +3 -2
- package/plugins/paoding-config-ui/api-core.mjs +171 -2
- package/plugins/paoding-config-ui/icon.svg +8 -0
- package/plugins/paoding-config-ui/index.mjs +88 -6
- package/plugins/paoding-config-ui/lib/base.css +244 -0
- package/plugins/paoding-config-ui/lib/client.js +517 -46
- package/plugins/paoding-config-ui/package.json +7 -2
- package/plugins/paoding-config-ui/runtime-inventory.mjs +22 -0
- package/plugins/paoding-config-ui/stats-core.mjs +362 -0
- package/presets/orchestrator/agent.cordis.yml +2 -1
- package/presets/orchestrator/restrict.mjs +1 -1
- package/tools/lib/cli.mjs +298 -8
- package/tools/lib/config.mjs +117 -0
- package/tools/lib/doctor.mjs +3 -13
- package/tools/lib/lifecycle.mjs +264 -0
|
@@ -93,6 +93,7 @@ window.__ModuleLoader__.load({
|
|
|
93
93
|
// ver.*(版本更新卡)/ models.* / tpl.*(场景模板)/ ws.*(工作区)/
|
|
94
94
|
// panel.*(配置面板正文)/ role.*(角色卡与委派行标题)/ warn.*(体检
|
|
95
95
|
// 告警卡)/ nav.*(侧栏入口与页头壳)/ menu.*(页头「更多」菜单与升级)/
|
|
96
|
+
// slim.*(工具面瘦身统计)/ diff.*(预览「本次变更」结构化差分)/
|
|
96
97
|
// common.*(跨片段复用的短词)。
|
|
97
98
|
// - t(key, params):按当前语言取文案,params 做 {name} 插值(split/join
|
|
98
99
|
// 而非 replace,避免替换串里的 $ 模式被二次解释)。缺 key 原样返回 key
|
|
@@ -449,10 +450,67 @@ window.__ModuleLoader__.load({
|
|
|
449
450
|
"menu.upgradeConfirm": { zh: "升级", en: "Upgrade" },
|
|
450
451
|
"menu.upgradeDevBody": { zh: "当前为开发 link 直连形态:无法就地升级,请手动执行 dsh plugin add dsh-paoding@latest 切换到 registry 版本。仍要继续尝试吗?", en: "This is a dev link checkout: it cannot be upgraded in place. Run dsh plugin add dsh-paoding@latest manually to switch to the registry version. Continue anyway?" },
|
|
451
452
|
"menu.upgradeBody": { zh: "确定升级到 {v}?升级将通过插件通道原地换版本,完成后需重启 DSH 生效。", en: "Upgrade to {v}? The upgrade swaps the version in place through the plugin channel; restart DSH afterwards to apply it." },
|
|
453
|
+
"menu.upgradeBodyHot": { zh: "确定升级到 {v}?升级将通过插件通道原地换版本,完成后刷新页面即生效(编排预设自动按新版重生成)。", en: "Upgrade to {v}? The upgrade swaps the version in place through the plugin channel; refresh the page afterwards — orchestration presets regenerate automatically." },
|
|
452
454
|
"menu.upToDate": { zh: "已是最新版本", en: "Already up to date" },
|
|
453
455
|
"menu.upgraded": { zh: "✅ 已升级到 {v},重启 DSH 后新版本生效(preset 会自动按新版重生成)", en: "✅ Upgraded to {v} — restart DSH to apply the new version (the preset regenerates itself against it)" },
|
|
456
|
+
"menu.upgradedHot": { zh: "✅ 已升级到 {v},刷新页面即生效(preset 会自动按新版重生成)", en: "✅ Upgraded to {v} — refresh the page to apply (the preset regenerates itself against it)" },
|
|
454
457
|
"menu.errTail": { zh: ":{tail}", en: ": {tail}" },
|
|
455
458
|
"menu.langTip": { zh: "切换界面语言", en: "Switch UI language" },
|
|
459
|
+
|
|
460
|
+
// ── 工具筛选(角色卡工具网格超过 8 项时,网格上方的行内筛选框)─────────
|
|
461
|
+
"panel.toolFilter": { zh: "筛选工具…", en: "Filter tools…" },
|
|
462
|
+
|
|
463
|
+
// ── 工具面瘦身统计(state.slim / preview.slim 的一行统计)──────────────
|
|
464
|
+
"slim.line": { zh: "主 agent 工具面 {main}/{total} 个(瘦身 {pct}%)", en: "Main agent tool surface: {main}/{total} tools (down {pct}%)" },
|
|
465
|
+
// 预览弹窗里的第二口径小注:preview.slim 按本次提交(未保存)配置计算,
|
|
466
|
+
// 与页头按已保存配置的一行区分开。
|
|
467
|
+
"slim.pendingNote": { zh: "(按当前未保存配置)", en: " (per the current unsaved config)" },
|
|
468
|
+
|
|
469
|
+
// ── 委派效果卡(GET stats 的只读紧凑表,页头瘦身行之下)───────────────
|
|
470
|
+
"del.cardTitle": { zh: "委派效果", en: "Delegation stats" },
|
|
471
|
+
"del.colRole": { zh: "角色", en: "Role" },
|
|
472
|
+
"del.colCalls": { zh: "调用", en: "Calls" },
|
|
473
|
+
"del.colAvg": { zh: "均耗时", en: "Avg" },
|
|
474
|
+
"del.colFailed": { zh: "失败", en: "Failed" },
|
|
475
|
+
"del.colLastUsed": { zh: "最近使用", en: "Last used" },
|
|
476
|
+
"del.totalRoles": { zh: "共 {n} 个角色", en: "{n} roles in total" },
|
|
477
|
+
"del.empty": { zh: "暂无委派记录", en: "No delegation records yet" },
|
|
478
|
+
|
|
479
|
+
// ── 预览「本次变更」区块(preview.diff 结构化差分的渲染文案)───────────
|
|
480
|
+
// 服务端 diff 是纯结构零文案(tools/lib/config.mjs diffAssignments),
|
|
481
|
+
// 全部措辞在这里收口;mainAgent.modelChanged 恒 false,不设键不渲染。
|
|
482
|
+
"diff.heading": { zh: "本次变更", en: "Changes this time" },
|
|
483
|
+
"diff.unchanged": { zh: "与上次保存的配置一致", en: "Identical to the last saved config." },
|
|
484
|
+
"diff.mainToolsAdded": { zh: "主 agent 新带 {n} 个工具:{list}", en: "Main agent now carries {n} more tools: {list}" },
|
|
485
|
+
"diff.mainToolsRemoved": { zh: "主 agent 移除 {n} 个工具:{list}", en: "Main agent drops {n} tools: {list}" },
|
|
486
|
+
"diff.mainPersona": { zh: "主 agent 人设追加有变化", en: "Main agent persona addendum changed" },
|
|
487
|
+
"diff.mainSkills": { zh: "主 agent 技能分配有变化", en: "Main agent skill assignment changed" },
|
|
488
|
+
"diff.roleAdded": { zh: "新增角色「{name}」", en: "Role \"{name}\" added" },
|
|
489
|
+
"diff.roleRemoved": { zh: "移除角色「{name}」", en: "Role \"{name}\" removed" },
|
|
490
|
+
"diff.roleChanged": { zh: "角色「{name}」变更:{details}", en: "Role \"{name}\" changed: {details}" },
|
|
491
|
+
"diff.roleTools": { zh: "工具 +{added} -{removed}", en: "tools +{added} -{removed}" },
|
|
492
|
+
"diff.rolePersona": { zh: "persona 变化", en: "persona changed" },
|
|
493
|
+
"diff.roleModel": { zh: "专用模型变化", en: "dedicated model changed" },
|
|
494
|
+
"diff.roleStartMode": { zh: "启动方式变化", en: "start mode changed" },
|
|
495
|
+
"diff.roleBgMode": { zh: "后台续跑变化", en: "background session mode changed" },
|
|
496
|
+
"diff.rolesRemoveAdded": { zh: "移除内置角色 {n} 个:{list}", en: "Removed {n} built-in roles: {list}" },
|
|
497
|
+
"diff.rolesRemoveRemoved": { zh: "恢复内置角色 {n} 个:{list}", en: "Restored {n} built-in roles: {list}" },
|
|
498
|
+
// main_agent_extra 通道明细(主 agent 合并视图之外的通道拆分,一并列出)
|
|
499
|
+
"diff.mainExtraAdded": { zh: "主 agent 附加工具新增 {n} 个:{list}", en: "Main agent extra tools added ({n}): {list}" },
|
|
500
|
+
"diff.mainExtraRemoved": { zh: "主 agent 附加工具移除 {n} 个:{list}", en: "Main agent extra tools removed ({n}): {list}" },
|
|
501
|
+
|
|
502
|
+
// ── 恢复备份(页头「更多」菜单 → 备份列表弹层 → 二次确认 → POST restore)
|
|
503
|
+
"menu.restoreItem": { zh: "恢复备份…", en: "Restore backup…" },
|
|
504
|
+
"menu.backupTitle": { zh: "恢复备份", en: "Restore backup" },
|
|
505
|
+
"menu.backupLoading": { zh: "加载备份列表…", en: "Loading backups…" },
|
|
506
|
+
"menu.backupEmpty": { zh: "暂无备份", en: "No backups yet" },
|
|
507
|
+
"menu.backupBadResponse": { zh: "备份列表响应格式异常", en: "Unexpected backup list response" },
|
|
508
|
+
"menu.backupListFail": { zh: "备份列表拉取失败:{msg}", en: "Failed to load backups: {msg}" },
|
|
509
|
+
"menu.restoreTitle": { zh: "确认恢复此备份", en: "Restore this backup" },
|
|
510
|
+
"menu.restoreBody": { zh: "将用备份「{name}」覆盖当前配置,覆盖前当前配置会先自动备份。", en: "Backup \"{name}\" will overwrite the current config; the current config is backed up automatically first." },
|
|
511
|
+
"menu.restoreConfirm": { zh: "覆盖恢复", en: "Overwrite & restore" },
|
|
512
|
+
"menu.restored": { zh: "已恢复备份 {name},恢复前的配置已自动存为 bak-1", en: "Restored backup {name} — the previous config was saved as bak-1 automatically" },
|
|
513
|
+
"menu.restoreFail": { zh: "恢复失败:{msg}", en: "Restore failed: {msg}" },
|
|
456
514
|
};
|
|
457
515
|
// ── 样式注入(幂等)─────────────────────────────────────────────────
|
|
458
516
|
//
|
|
@@ -496,8 +554,10 @@ window.__ModuleLoader__.load({
|
|
|
496
554
|
|
|
497
555
|
// projBadge(可选):项目级技能角标文案(如「项目」),仅技能项在
|
|
498
556
|
// 工作区视角下、该技能来自工作区项目根时传入;其余网格不传不渲染。
|
|
557
|
+
// data-pd-tool:工具显示名打点——供 applyToolFilter 做筛选匹配(q 透传
|
|
558
|
+
// getAttribute,React 不感知),与 title 悬浮提示内容一致。
|
|
499
559
|
function toolItem(tool, tag, checked, disabled, onChange, key, projBadge) {
|
|
500
|
-
return h("label", { key: key, className: "omd-toolItem", title: tool },
|
|
560
|
+
return h("label", { key: key, className: "omd-toolItem", title: tool, "data-pd-tool": tool },
|
|
501
561
|
h("input", {
|
|
502
562
|
type: "checkbox",
|
|
503
563
|
className: "omd-check",
|
|
@@ -511,6 +571,132 @@ window.__ModuleLoader__.load({
|
|
|
511
571
|
projBadge ? h("span", { className: "omd-skillProj" }, projBadge) : null);
|
|
512
572
|
}
|
|
513
573
|
|
|
574
|
+
// ── 工具网格筛选(超过 8 项的分组在网格上方给一个行内筛选框)────────────
|
|
575
|
+
// 触发阈值:分组超过 8 项才出现筛选框(短网格不必多一个输入框)。
|
|
576
|
+
var TOOL_FILTER_MIN = 8;
|
|
577
|
+
|
|
578
|
+
// 工具网格(可选筛选框 + 网格本体):items 超过阈值时在外面包一层
|
|
579
|
+
// .omd-toolFilterWrap(输入框 + 网格),否则原样返回网格节点。不引入任何
|
|
580
|
+
// 状态管道——筛选 oninput 直接改 DOM 显隐(见 applyToolFilter),整卡重
|
|
581
|
+
// 渲染会吃掉输入框焦点,这个交互必须绕开 React。
|
|
582
|
+
// renderItem:单工具 → 节点(toolItem 调用点原样搬进来);emptyNode:
|
|
583
|
+
// 空列表占位(原「暂无 …」节点),空组照旧渲染、无筛选框。空占位作直接
|
|
584
|
+
// 子节点传入(不进数组),与改造前调用点的 children 形状一致,不触发
|
|
585
|
+
// React 的数组 key 警告。
|
|
586
|
+
function toolGridOf(tools, renderItem, emptyNode) {
|
|
587
|
+
var grid;
|
|
588
|
+
if (tools.length) {
|
|
589
|
+
grid = h.apply(null, ["div", { className: "omd-toolGrid" }].concat(tools.map(renderItem)));
|
|
590
|
+
} else {
|
|
591
|
+
grid = h("div", { className: "omd-toolGrid" }, emptyNode || null);
|
|
592
|
+
}
|
|
593
|
+
if (tools.length <= TOOL_FILTER_MIN) return grid;
|
|
594
|
+
return h("div", { className: "omd-toolFilterWrap" },
|
|
595
|
+
h("input", {
|
|
596
|
+
type: "text",
|
|
597
|
+
className: "omd-toolFilter",
|
|
598
|
+
placeholder: t("panel.toolFilter"),
|
|
599
|
+
spellCheck: false,
|
|
600
|
+
onInput: function (e) { applyToolFilter(e.target); },
|
|
601
|
+
}),
|
|
602
|
+
grid);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// 筛选 oninput:从输入框向上取包裹层,按 data-pd-tool(工具显示名)做
|
|
606
|
+
// 大小写不敏感子串匹配,逐项切 display——纯 DOM 操作不触发重渲染,输入框
|
|
607
|
+
// 焦点不丢;清空输入恢复全部显示。
|
|
608
|
+
function applyToolFilter(input) {
|
|
609
|
+
var wrap = input && input.parentNode;
|
|
610
|
+
if (!wrap || !wrap.querySelectorAll) return;
|
|
611
|
+
var q = String(input.value || "").toLowerCase();
|
|
612
|
+
var items = wrap.querySelectorAll("label[data-pd-tool]");
|
|
613
|
+
for (var i = 0; i < items.length; i++) {
|
|
614
|
+
var name = items[i].getAttribute("data-pd-tool") || "";
|
|
615
|
+
items[i].style.display = q !== "" && name.toLowerCase().indexOf(q) === -1 ? "none" : "";
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// ── 工具面瘦身统计(state.slim / preview.slim → 一行文案)───────────────
|
|
620
|
+
// 瘦身百分比:pct = total>0 ? round((1-main/total)*100) : 0(total=0 防除零)。
|
|
621
|
+
function slimPctOf(slim) {
|
|
622
|
+
var total = slim && typeof slim.total === "number" ? slim.total : 0;
|
|
623
|
+
var main = slim && typeof slim.main === "number" ? slim.main : 0;
|
|
624
|
+
return total > 0 ? Math.round((1 - main / total) * 100) : 0;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// 一行统计文案(页头与预览弹窗共用同一拼法,防漂移)。
|
|
628
|
+
function slimLineText(slim) {
|
|
629
|
+
return t("slim.line", {
|
|
630
|
+
main: slim && typeof slim.main === "number" ? slim.main : 0,
|
|
631
|
+
total: slim && typeof slim.total === "number" ? slim.total : 0,
|
|
632
|
+
pct: slimPctOf(slim),
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// ── 委派效果卡(GET stats → 页头瘦身行之下的只读紧凑表)────────────────
|
|
637
|
+
// 数据由 86 PageShell 首屏渲染完后懒加载(不阻塞开面板);服务缺失 / 拉取
|
|
638
|
+
// 失败 / 响应异常时整卡不渲染(delegationCardOf 返回 null),不打扰。
|
|
639
|
+
|
|
640
|
+
// 均耗时人话格式:1 分内「1.2s」(两位数以上取整秒),1 小时内「3m05s」,
|
|
641
|
+
// 更长「2h07m」。非正数一律 0s。
|
|
642
|
+
function delegDurationText(ms) {
|
|
643
|
+
if (!(ms > 0)) return "0s";
|
|
644
|
+
var s = ms / 1000;
|
|
645
|
+
if (s < 60) return (s >= 10 ? String(Math.round(s)) : s.toFixed(1)) + "s";
|
|
646
|
+
var m = Math.floor(s / 60);
|
|
647
|
+
if (m < 60) return m + "m" + String(Math.round(s % 60)).padStart(2, "0") + "s";
|
|
648
|
+
return Math.floor(m / 60) + "h" + String(m % 60).padStart(2, "0") + "m";
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// 最近使用时间:当年「MM-DD HH:mm」,跨年「YYYY-MM-DD」——纯数字中性格式,
|
|
652
|
+
// 不进字典也不吃 i18n。
|
|
653
|
+
function delegWhenText(ms) {
|
|
654
|
+
var d = new Date(ms);
|
|
655
|
+
if (isNaN(d.getTime())) return "—";
|
|
656
|
+
var two = function (n) { return String(n).padStart(2, "0"); };
|
|
657
|
+
var now = new Date();
|
|
658
|
+
if (d.getFullYear() !== now.getFullYear()) {
|
|
659
|
+
return d.getFullYear() + "-" + two(d.getMonth() + 1) + "-" + two(d.getDate());
|
|
660
|
+
}
|
|
661
|
+
return two(d.getMonth() + 1) + "-" + two(d.getDate()) + " " + two(d.getHours()) + ":" + two(d.getMinutes());
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// 卡片渲染:stats 缺失 / ok!==true / roles 非数组 → null(整卡零占位)。
|
|
665
|
+
// delegatedSessions=0 渲染标题 + 一行空文案;有数据渲染紧凑表(表头 +
|
|
666
|
+
// 最多 8 行,超出给「共 N 个角色」尾行),失败数 >0 标红提醒。
|
|
667
|
+
function delegationCardOf(stats) {
|
|
668
|
+
if (!stats || stats.ok !== true || !Array.isArray(stats.roles)) return null;
|
|
669
|
+
var head = h("div", { className: "pd-delegTitle" }, t("del.cardTitle"));
|
|
670
|
+
if (!stats.delegatedSessions) {
|
|
671
|
+
return h("div", { className: "pd-delegCard" }, head,
|
|
672
|
+
h("div", { className: "pd-delegEmpty" }, t("del.empty")));
|
|
673
|
+
}
|
|
674
|
+
var cols = [
|
|
675
|
+
["pd-delegRole", t("del.colRole")],
|
|
676
|
+
["pd-delegNum", t("del.colCalls")],
|
|
677
|
+
["pd-delegNum", t("del.colAvg")],
|
|
678
|
+
["pd-delegNum", t("del.colFailed")],
|
|
679
|
+
["pd-delegWhen", t("del.colLastUsed")],
|
|
680
|
+
];
|
|
681
|
+
var headerRow = h("div", { className: "pd-delegRow pd-delegHeaderRow" }, cols.map(function (c, i) {
|
|
682
|
+
return h("span", { key: "h" + i, className: c[0] }, c[1]);
|
|
683
|
+
}));
|
|
684
|
+
var shown = stats.roles.slice(0, 8);
|
|
685
|
+
var rows = shown.map(function (r) {
|
|
686
|
+
return h("div", { className: "pd-delegRow", key: r.role },
|
|
687
|
+
h("span", { className: "pd-delegRole", title: r.role }, r.role),
|
|
688
|
+
h("span", { className: "pd-delegNum" }, String(r.calls)),
|
|
689
|
+
h("span", { className: "pd-delegNum" }, delegDurationText(r.avgMs)),
|
|
690
|
+
h("span", { className: "pd-delegNum" + (r.failures > 0 ? " pd-delegFail" : "") }, String(r.failures)),
|
|
691
|
+
h("span", { className: "pd-delegWhen" }, delegWhenText(r.lastUsedAt)));
|
|
692
|
+
});
|
|
693
|
+
var tail = stats.roles.length > shown.length
|
|
694
|
+
? h("div", { className: "pd-delegMore" }, t("del.totalRoles", { n: stats.roles.length }))
|
|
695
|
+
: null;
|
|
696
|
+
return h("div", { className: "pd-delegCard" }, head,
|
|
697
|
+
h.apply(null, ["div", { className: "pd-delegTable" }, headerRow].concat(rows)), tail);
|
|
698
|
+
}
|
|
699
|
+
|
|
514
700
|
var DEFAULT_ROLES = ["search_external", "design", "implement"];
|
|
515
701
|
// 内置角色默认 persona 首行「身份主语」(仅用于 UI 占位与提示文案;实际生效值
|
|
516
702
|
// 由后端 SRC 模板 persona 首行决定)。「显示名」= 替换该主语(其余不动)。
|
|
@@ -1170,6 +1356,85 @@ window.__ModuleLoader__.load({
|
|
|
1170
1356
|
h("div", { className: "omd-section" }, rows));
|
|
1171
1357
|
}
|
|
1172
1358
|
|
|
1359
|
+
// ── 预览「本次变更」区块(preview.diff 结构化差分的渲染)─────────────────
|
|
1360
|
+
// 服务端 preview 响应的 diff 是纯结构零文案(tools/lib/config.mjs
|
|
1361
|
+
// diffAssignments),客户端在此按块渲染成可读行,全部措辞走 t() 字典:
|
|
1362
|
+
// - changed=false → 一行「与上次保存的配置一致」;
|
|
1363
|
+
// - changed=true → 主 agent(新带/移除工具 + persona/技能变化;modelChanged
|
|
1364
|
+
// 恒 false 不渲染)→ 各角色(新增/移除/变更,变更细分 +N -N 工具、
|
|
1365
|
+
// persona、专用模型、启动方式、后台续跑)→ roles_remove(删/恢复内置
|
|
1366
|
+
// 角色)→ main_extra(附加工具通道增删明细)逐块完整列出(预览区本就
|
|
1367
|
+
// 在整页滚动流里,行多不怕)。
|
|
1368
|
+
// apply 确认流程刻意不加本区块:预览才是「看一眼」的时刻,确认弹窗保持
|
|
1369
|
+
// 一屏说清。行按语义着色:净新增(绿)/ 净移除(红)/ 变更明细(中性)。
|
|
1370
|
+
function diffLineOf(text, tone) {
|
|
1371
|
+
return h("div", {
|
|
1372
|
+
className: "omd-diffLine" +
|
|
1373
|
+
(tone === "add" ? " omd-diffLineAdd" : tone === "del" ? " omd-diffLineDel" : ""),
|
|
1374
|
+
}, text);
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// 工具/角色名列表串(按既有 listSep 口径连接);调用方先判空再取。
|
|
1378
|
+
function diffListStr(list) {
|
|
1379
|
+
return (list || []).join(t("common.listSep"));
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
function diffBlockOf(diff) {
|
|
1383
|
+
if (!diff || typeof diff !== "object") return null; // 老服务端无 diff 字段:整块零占位
|
|
1384
|
+
var lines = [];
|
|
1385
|
+
if (diff.changed) {
|
|
1386
|
+
// 主 agent:extra 增删与 remove 增删已由服务端合并成净增减视图
|
|
1387
|
+
//(toolsAdded / toolsRemoved),直接按净变化渲染。
|
|
1388
|
+
var ma = diff.mainAgent || {};
|
|
1389
|
+
if (ma.toolsAdded && ma.toolsAdded.length) {
|
|
1390
|
+
lines.push(diffLineOf(t("diff.mainToolsAdded", { n: ma.toolsAdded.length, list: diffListStr(ma.toolsAdded) }), "add"));
|
|
1391
|
+
}
|
|
1392
|
+
if (ma.toolsRemoved && ma.toolsRemoved.length) {
|
|
1393
|
+
lines.push(diffLineOf(t("diff.mainToolsRemoved", { n: ma.toolsRemoved.length, list: diffListStr(ma.toolsRemoved) }), "del"));
|
|
1394
|
+
}
|
|
1395
|
+
if (ma.personaChanged) lines.push(diffLineOf(t("diff.mainPersona")));
|
|
1396
|
+
if (ma.skillsChanged) lines.push(diffLineOf(t("diff.mainSkills")));
|
|
1397
|
+
// 角色:新增 / 移除 / 变更(变更行细分到维度,逐维度本地化)。
|
|
1398
|
+
(diff.roles || []).forEach(function (r) {
|
|
1399
|
+
if (!r || !r.name) return;
|
|
1400
|
+
if (r.op === "added") { lines.push(diffLineOf(t("diff.roleAdded", { name: r.name }), "add")); return; }
|
|
1401
|
+
if (r.op === "removed") { lines.push(diffLineOf(t("diff.roleRemoved", { name: r.name }), "del")); return; }
|
|
1402
|
+
var details = [];
|
|
1403
|
+
if ((r.toolsAdded && r.toolsAdded.length) || (r.toolsRemoved && r.toolsRemoved.length)) {
|
|
1404
|
+
details.push(t("diff.roleTools", { added: (r.toolsAdded || []).length, removed: (r.toolsRemoved || []).length }));
|
|
1405
|
+
}
|
|
1406
|
+
if (r.personaChanged) details.push(t("diff.rolePersona"));
|
|
1407
|
+
if (r.modelChanged) details.push(t("diff.roleModel"));
|
|
1408
|
+
if (r.startModeChanged) details.push(t("diff.roleStartMode"));
|
|
1409
|
+
if (r.backgroundModeChanged) details.push(t("diff.roleBgMode"));
|
|
1410
|
+
if (details.length) {
|
|
1411
|
+
lines.push(diffLineOf(t("diff.roleChanged", { name: r.name, details: details.join(t("common.listSep")) })));
|
|
1412
|
+
}
|
|
1413
|
+
});
|
|
1414
|
+
// roles_remove:新增 = 删除的内置角色(净移除);移除 = 恢复(净新增)。
|
|
1415
|
+
var rr = diff.rolesRemove || {};
|
|
1416
|
+
if (rr.added && rr.added.length) {
|
|
1417
|
+
lines.push(diffLineOf(t("diff.rolesRemoveAdded", { n: rr.added.length, list: diffListStr(rr.added) }), "del"));
|
|
1418
|
+
}
|
|
1419
|
+
if (rr.removed && rr.removed.length) {
|
|
1420
|
+
lines.push(diffLineOf(t("diff.rolesRemoveRemoved", { n: rr.removed.length, list: diffListStr(rr.removed) }), "add"));
|
|
1421
|
+
}
|
|
1422
|
+
// main_extra 通道明细(主 agent 合并视图之外的通道拆分,按任务要求一并列出)。
|
|
1423
|
+
var me = diff.mainExtra || {};
|
|
1424
|
+
if (me.added && me.added.length) {
|
|
1425
|
+
lines.push(diffLineOf(t("diff.mainExtraAdded", { n: me.added.length, list: diffListStr(me.added) }), "add"));
|
|
1426
|
+
}
|
|
1427
|
+
if (me.removed && me.removed.length) {
|
|
1428
|
+
lines.push(diffLineOf(t("diff.mainExtraRemoved", { n: me.removed.length, list: diffListStr(me.removed) }), "del"));
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
return h("div", { className: "omd-diffBlock" },
|
|
1432
|
+
h("div", { className: "omd-diffHeading" }, t("diff.heading")),
|
|
1433
|
+
diff.changed && lines.length
|
|
1434
|
+
? h("div", { className: "omd-diffLines" }, lines)
|
|
1435
|
+
: h("div", { className: "omd-diffNone" }, t("diff.unchanged")));
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1173
1438
|
// ── 主面板 ─────────────────────────────────────────────────────────────
|
|
1174
1439
|
|
|
1175
1440
|
function ConfigPanel(props) {
|
|
@@ -1197,6 +1462,11 @@ window.__ModuleLoader__.load({
|
|
|
1197
1462
|
// 状态挂在页头 PageShell(版本号展示在标题旁),经 props 下发:
|
|
1198
1463
|
// setVerInfo 供 loadVersion 回写页头(版本号与更新卡都在 PageShell)。
|
|
1199
1464
|
var setVerInfo = props && props.setVerInfo;
|
|
1465
|
+
// 工具面瘦身统计(state.slim = 按已保存配置算的 {total, main}):呈现区
|
|
1466
|
+
// 同样挂在页头 PageShell(副标题下一行,版本号/更新卡同区),经 props
|
|
1467
|
+
// 下发 setSlimInfo 供 load / doRescan / doApply 回写。null = 老服务端
|
|
1468
|
+
// 无该字段 / 尚未取到,页头一行零占位。
|
|
1469
|
+
var setSlimInfo = props && props.setSlimInfo;
|
|
1200
1470
|
// 导入配置后的整体刷新入口:ConfigPanel 把 load 注册给页头(PageShell 的
|
|
1201
1471
|
// 「更多 → 导入配置」成功后调用)。面板与页头分属两棵子树,props 只能
|
|
1202
1472
|
// 单向传「注册函数」,刷新动作反过来经 ref 回调触达。
|
|
@@ -1344,6 +1614,9 @@ window.__ModuleLoader__.load({
|
|
|
1344
1614
|
api("state").then(function (d) {
|
|
1345
1615
|
setData(d);
|
|
1346
1616
|
setAssign(buildTargetAssignments(d, activeWsRef.current));
|
|
1617
|
+
// 页头瘦身统计跟着 state 走(state.slim 按已保存的全局配置计算,与
|
|
1618
|
+
// 工作区视角无关);老服务端无 slim 字段时置 null 隐藏该行。
|
|
1619
|
+
if (typeof setSlimInfo === "function") setSlimInfo(d && d.slim ? d.slim : null);
|
|
1347
1620
|
}).catch(function (err) { setError(err.message); });
|
|
1348
1621
|
}, [api, buildTargetAssignments]);
|
|
1349
1622
|
|
|
@@ -1988,6 +2261,10 @@ window.__ModuleLoader__.load({
|
|
|
1988
2261
|
return w && (w.level === "error" || w.level === "warn");
|
|
1989
2262
|
}).length;
|
|
1990
2263
|
var warnTail = t("panel.warnTail", { n: warnItems.length });
|
|
2264
|
+
// 全局应用成功:页头瘦身统计改按本次提交配置(apply 响应自带
|
|
2265
|
+
// slim,与落盘内容同口径);工作区应用不动全局配置,下方 state
|
|
2266
|
+
// 回拉自带 slim,不在额外设值。
|
|
2267
|
+
if (!wsPath && r && r.slim && typeof setSlimInfo === "function") setSlimInfo(r.slim);
|
|
1991
2268
|
if (wsPath) {
|
|
1992
2269
|
// 工作区应用:点明生成的 preset id,提示去预设选择器选用(工作区
|
|
1993
2270
|
// 预设不会自动成为默认,须在新建会话时显式选择)。体检 bad 时
|
|
@@ -2001,6 +2278,7 @@ window.__ModuleLoader__.load({
|
|
|
2001
2278
|
api("state").then(function (d) {
|
|
2002
2279
|
setData(d);
|
|
2003
2280
|
setAssign(buildTargetAssignments(d, activeWsRef.current));
|
|
2281
|
+
if (typeof setSlimInfo === "function") setSlimInfo(d && d.slim ? d.slim : null);
|
|
2004
2282
|
}).catch(function () { /* 静默:落盘已成功,仅展示层暂旧 */ });
|
|
2005
2283
|
} else {
|
|
2006
2284
|
var gMsg = t("panel.appliedGlobal", { summary: badCount > 0 ? warnTail : summary })
|
|
@@ -2032,6 +2310,8 @@ window.__ModuleLoader__.load({
|
|
|
2032
2310
|
api("rescan", {}).then(function (d) {
|
|
2033
2311
|
setData(d);
|
|
2034
2312
|
setAssign(buildTargetAssignments(d, activeWsRef.current));
|
|
2313
|
+
// 重扫后的 state 自带瘦身统计,页头一行同步刷新(字段缺失则隐藏)。
|
|
2314
|
+
if (typeof setSlimInfo === "function") setSlimInfo(d && d.slim ? d.slim : null);
|
|
2035
2315
|
// 回调参数 d 即重扫后的新 state,直接报数字,补回原徽标条的信息量
|
|
2036
2316
|
toast.ok(t("panel.rescanned", { n: (d.inventory || []).length, m: (d.skills || []).length }));
|
|
2037
2317
|
}).catch(function (err) { toast.err(err.message); }).finally(function () {
|
|
@@ -2927,11 +3207,12 @@ window.__ModuleLoader__.load({
|
|
|
2927
3207
|
if (isMain) {
|
|
2928
3208
|
body.push(mainNameBlock());
|
|
2929
3209
|
body.push(h("div", { className: "omd-glabel" }, t("panel.baseToolsMain")));
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
3210
|
+
// 三组工具网格都走 toolGridOf:超过 8 项的组自动带行内筛选框
|
|
3211
|
+
//(oninput 纯 DOM 切显隐,见 20 片段 toolGridOf / applyToolFilter)。
|
|
3212
|
+
body.push(toolGridOf(data.restrictBase || [], function (t) {
|
|
3213
|
+
return toolItem(t, "base", (a.main_agent_remove || []).indexOf(t) === -1, busy,
|
|
3214
|
+
function (e) { setMainRemove(t, e.target.checked); }, t);
|
|
3215
|
+
}));
|
|
2935
3216
|
// host 检测库存按角色卡同口径拆两组:mcp__ 前缀 → 「MCP 工具」,
|
|
2936
3217
|
// 其余(mnemon_* 等插件直注册工具)→ 「插件工具」。勾选逻辑不变,
|
|
2937
3218
|
// 都写入 main_agent_extra。
|
|
@@ -2939,21 +3220,15 @@ window.__ModuleLoader__.load({
|
|
|
2939
3220
|
var mainMcpTools = mainInv.filter(function (t) { return hostTool(t); });
|
|
2940
3221
|
var mainPluginTools = mainInv.filter(function (t) { return !hostTool(t); });
|
|
2941
3222
|
body.push(h("div", { className: "omd-glabel" }, t("panel.mcpToolsMain")));
|
|
2942
|
-
body.push(
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
function (e) { setMainExtra(t, e.target.checked); }, t);
|
|
2947
|
-
})
|
|
2948
|
-
: h("div", { className: "omd-empty" }, t("panel.noMcp"))));
|
|
3223
|
+
body.push(toolGridOf(mainMcpTools, function (t) {
|
|
3224
|
+
return toolItem(t, "mcp", a.main_agent_extra.indexOf(t) !== -1, busy,
|
|
3225
|
+
function (e) { setMainExtra(t, e.target.checked); }, t);
|
|
3226
|
+
}, h("div", { className: "omd-empty" }, t("panel.noMcp"))));
|
|
2949
3227
|
body.push(h("div", { className: "omd-glabel" }, t("panel.pluginToolsMain")));
|
|
2950
|
-
body.push(
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
function (e) { setMainExtra(t, e.target.checked); }, t);
|
|
2955
|
-
})
|
|
2956
|
-
: h("div", { className: "omd-empty" }, t("panel.noPlugin"))));
|
|
3228
|
+
body.push(toolGridOf(mainPluginTools, function (t) {
|
|
3229
|
+
return toolItem(t, "plugin", a.main_agent_extra.indexOf(t) !== -1, busy,
|
|
3230
|
+
function (e) { setMainExtra(t, e.target.checked); }, t);
|
|
3231
|
+
}, h("div", { className: "omd-empty" }, t("panel.noPlugin"))));
|
|
2957
3232
|
body.push(h("div", { className: "omd-glabel" }, t("panel.skillsMain")));
|
|
2958
3233
|
body.push(gridSkills.length
|
|
2959
3234
|
? h("div", null,
|
|
@@ -2995,30 +3270,22 @@ window.__ModuleLoader__.load({
|
|
|
2995
3270
|
var pluginTools = candidates.filter(function (t) { return !hostTool(t) && inv.indexOf(t) !== -1; });
|
|
2996
3271
|
var baseTools = candidates.filter(function (t) { return !hostTool(t) && inv.indexOf(t) === -1; });
|
|
2997
3272
|
var checked = a.roles[name].tools;
|
|
3273
|
+
// 三组网格同走 toolGridOf(超 8 项自动带筛选框,与主 agent 卡同款)。
|
|
2998
3274
|
body.push(h("div", { className: "omd-glabel" }, t("panel.baseTools")));
|
|
2999
|
-
body.push(
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
function (e) { setRoleTools(name, t, e.target.checked); }, t);
|
|
3004
|
-
})
|
|
3005
|
-
: h("div", { className: "omd-empty" }, t("panel.none"))));
|
|
3275
|
+
body.push(toolGridOf(baseTools, function (t) {
|
|
3276
|
+
return toolItem(t, "base", checked.indexOf(t) !== -1, busy,
|
|
3277
|
+
function (e) { setRoleTools(name, t, e.target.checked); }, t);
|
|
3278
|
+
}, h("div", { className: "omd-empty" }, t("panel.none"))));
|
|
3006
3279
|
body.push(h("div", { className: "omd-glabel" }, t("panel.mcpTools")));
|
|
3007
|
-
body.push(
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
function (e) { setRoleTools(name, t, e.target.checked); }, t);
|
|
3012
|
-
})
|
|
3013
|
-
: h("div", { className: "omd-empty" }, t("panel.noMcp"))));
|
|
3280
|
+
body.push(toolGridOf(mcpTools, function (t) {
|
|
3281
|
+
return toolItem(t, "mcp", checked.indexOf(t) !== -1, busy,
|
|
3282
|
+
function (e) { setRoleTools(name, t, e.target.checked); }, t);
|
|
3283
|
+
}, h("div", { className: "omd-empty" }, t("panel.noMcp"))));
|
|
3014
3284
|
body.push(h("div", { className: "omd-glabel" }, t("panel.pluginTools")));
|
|
3015
|
-
body.push(
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
function (e) { setRoleTools(name, t, e.target.checked); }, t);
|
|
3020
|
-
})
|
|
3021
|
-
: h("div", { className: "omd-empty" }, t("panel.noPlugin"))));
|
|
3285
|
+
body.push(toolGridOf(pluginTools, function (t) {
|
|
3286
|
+
return toolItem(t, "plugin", checked.indexOf(t) !== -1, busy,
|
|
3287
|
+
function (e) { setRoleTools(name, t, e.target.checked); }, t);
|
|
3288
|
+
}, h("div", { className: "omd-empty" }, t("panel.noPlugin"))));
|
|
3022
3289
|
body.push(personaBlock(name));
|
|
3023
3290
|
}
|
|
3024
3291
|
|
|
@@ -3201,6 +3468,14 @@ window.__ModuleLoader__.load({
|
|
|
3201
3468
|
(preview.roleResults || []).map(function (r) {
|
|
3202
3469
|
return " · " + r.role + ": " + r.intent.length + " → " + r.kept.length;
|
|
3203
3470
|
}).join("")),
|
|
3471
|
+
// 「本次变更」区块:服务端 diff 到场才渲染(老服务端零占位);
|
|
3472
|
+
// unchanged 一行、changed 完整列出(见 70 片段 diffBlockOf)。
|
|
3473
|
+
diffBlockOf(preview.diff),
|
|
3474
|
+
// 瘦身统计(按本次提交配置算的预览口径):与页头「按已保存配置」的
|
|
3475
|
+
// 一行区分,随行标注「按当前未保存配置」。apply 确认流程不加区块。
|
|
3476
|
+
preview.slim ? h("div", { className: "omd-slimLine" },
|
|
3477
|
+
slimLineText(preview.slim),
|
|
3478
|
+
h("span", { className: "omd-slimNote" }, t("slim.pendingNote"))) : null,
|
|
3204
3479
|
h("pre", { className: "omd-pre" }, preview.text)) : null,
|
|
3205
3480
|
|
|
3206
3481
|
// 悬浮操作坞(sticky bottom:按钮常驻滚动容器底部,改完配置无需滚回顶部再操作)
|
|
@@ -3679,15 +3954,22 @@ window.__ModuleLoader__.load({
|
|
|
3679
3954
|
// 改通用确认弹窗,confirmCtrl 单例见 70 片段)文案按挂载形态分流:
|
|
3680
3955
|
// dev link 直连形态(layout 由服务端 version 路由按包根 .git 判定)无法
|
|
3681
3956
|
// 就地升级,把手动切换命令说在前头;installed(registry 版)原地换版本,
|
|
3682
|
-
//
|
|
3957
|
+
// 按宿主热重载能力(version 路由 hotReloadable:插件清单里有启用的
|
|
3958
|
+
// dsh-hmr 行)再分两级——有则「刷新页面即生效」,无则「重启 DSH 生效」。
|
|
3959
|
+
// 反馈(成功 note / 失败 err,含子进程输出尾部)都收在卡内,不与
|
|
3683
3960
|
// 面板的 toast 反馈混流。升级非破坏性操作,danger 不置位;弹窗本体由
|
|
3684
3961
|
// PageShell 末尾的 ConfirmLayer 渲染(页头与面板发起的确认共用一层)。
|
|
3685
3962
|
function runUpgradeFlow(verInfo, setVerInfo, setUpgBusy, setUpgNote, setUpgErr) {
|
|
3686
3963
|
if (!verInfo || !verInfo.latest) return;
|
|
3687
3964
|
var isDev = verInfo.layout === "dev";
|
|
3965
|
+
// 二级分流:宿主插件清单里有启用的 dsh-hmr 插件行(0.2.1+ web/desktop
|
|
3966
|
+
// 发布 profile 默认带上,version 路由的 hotReloadable 字段)时,插件
|
|
3967
|
+
// 通道原地换版本后刷新页面即生效,无需重启 DSH——确认框与成功 note
|
|
3968
|
+
// 都换「刷新即生效」文案;字段缺失 / 探测失败回落「需重启」文案。
|
|
3969
|
+
var hot = verInfo.hotReloadable === true;
|
|
3688
3970
|
var msg = isDev
|
|
3689
3971
|
? t("menu.upgradeDevBody")
|
|
3690
|
-
: t("menu.upgradeBody", { v: versionTagOf(verInfo.latest) });
|
|
3972
|
+
: t(hot ? "menu.upgradeBodyHot" : "menu.upgradeBody", { v: versionTagOf(verInfo.latest) });
|
|
3691
3973
|
confirmCtrl.ask({
|
|
3692
3974
|
title: t("menu.upgradeTitle"),
|
|
3693
3975
|
body: msg,
|
|
@@ -3702,7 +3984,7 @@ window.__ModuleLoader__.load({
|
|
|
3702
3984
|
var done = versionTagOf((r && r.version) || verInfo.latest);
|
|
3703
3985
|
// 服务端返回了 message 就原文透传(服务端消息不做客户端翻译),
|
|
3704
3986
|
// 没有才落本地化兜底文案。
|
|
3705
|
-
setUpgNote((r && r.message) || t("menu.upgraded", { v: done }));
|
|
3987
|
+
setUpgNote((r && r.message) || t(hot ? "menu.upgradedHot" : "menu.upgraded", { v: done }));
|
|
3706
3988
|
// 重拉版本信息:服务端有 1 小时缓存且本进程挂的还是旧代码,重启前
|
|
3707
3989
|
// 更新卡可能仍在(重启后自然消失),以此处的生效提示为准。
|
|
3708
3990
|
paodingApi("version").then(function (d) {
|
|
@@ -3720,6 +4002,41 @@ window.__ModuleLoader__.load({
|
|
|
3720
4002
|
});
|
|
3721
4003
|
}
|
|
3722
4004
|
|
|
4005
|
+
// ── 恢复备份(页头「更多」菜单 → 备份列表弹层 → 二次确认 → POST restore)──
|
|
4006
|
+
// 服务端契约(api-core.mjs / index.mjs 已落地):
|
|
4007
|
+
// GET config/backups → { ok, backups: [{file, mtimeMs, size}] }(新→旧,可空)
|
|
4008
|
+
// POST config/restore { file } → { ok };422 文件名非法/备份坏、404 不存在、
|
|
4009
|
+
// 409 有 apply 在跑({ error } 信封,客户端按既有错误 toast 惯例呈现)。
|
|
4010
|
+
// 行三段:bak-N 徽标(从文件名 .bak-N 尾缀解析,解析不出原样显示文件名)+
|
|
4011
|
+
// 本地时间(mtimeMs 按 UI 语言格式化)+ 大小(B/KB/MB 三档)。
|
|
4012
|
+
|
|
4013
|
+
// 备份徽标:file 恒为 basename(服务端保证),形如
|
|
4014
|
+
// 「<配置文件名>.bak-1」→ 「bak-1」;形状不对原样透出(不猜)。
|
|
4015
|
+
function backupTagOf(file) {
|
|
4016
|
+
var m = String(file || "").match(/\.bak-(\d+)$/);
|
|
4017
|
+
return m ? "bak-" + m[1] : String(file || "");
|
|
4018
|
+
}
|
|
4019
|
+
|
|
4020
|
+
// 本地时间:toLocaleString 按当前 UI 语言取 locale(zh → zh-CN / en → en-US
|
|
4021
|
+
// ——Electron 外壳 navigator 语言被钉成应用 locale,不可作格式化依据),
|
|
4022
|
+
// 24 小时制免「上午/下午」占宽;Intl 缺失的老内核逐层回落实现缺省格式。
|
|
4023
|
+
function backupTimeText(ms) {
|
|
4024
|
+
var d = new Date(typeof ms === "number" ? ms : 0);
|
|
4025
|
+
try {
|
|
4026
|
+
return d.toLocaleString(pdLang() === "en" ? "en-US" : "zh-CN", { hour12: false });
|
|
4027
|
+
} catch (err) {
|
|
4028
|
+
try { return d.toLocaleString(); } catch (err2) { return String(d); }
|
|
4029
|
+
}
|
|
4030
|
+
}
|
|
4031
|
+
|
|
4032
|
+
// 大小:B / KB / MB 三档(配置文件量级远用不上 GB),KB 以上留一位小数。
|
|
4033
|
+
function backupSizeText(bytes) {
|
|
4034
|
+
var n = typeof bytes === "number" && isFinite(bytes) && bytes >= 0 ? bytes : 0;
|
|
4035
|
+
if (n < 1024) return n + " B";
|
|
4036
|
+
if (n < 1024 * 1024) return (n / 1024).toFixed(1) + " KB";
|
|
4037
|
+
return (n / (1024 * 1024)).toFixed(1) + " MB";
|
|
4038
|
+
}
|
|
4039
|
+
|
|
3723
4040
|
// 确认弹窗层:订阅 70 片段的 confirmCtrl 单例(照 makeFooterEntry 的
|
|
3724
4041
|
// useState + subscribe 写法),渲染 confirmModalOf。挂在 PageShell 根数组
|
|
3725
4042
|
// 末尾——fixed 全屏遮罩覆盖整页,面板(删除角色 / 保存并应用)与页头
|
|
@@ -3763,6 +4080,22 @@ window.__ModuleLoader__.load({
|
|
|
3763
4080
|
// 版本检测重试进行中(页头失败行的「重试」按钮禁用态):与升级三态分开,
|
|
3764
4081
|
// 检测失败重试不该锁升级(升级预检在服务端另做一次,不依赖这份 verInfo)。
|
|
3765
4082
|
var _vb = useState(false), verRetryBusy = _vb[0], setVerRetryBusy = _vb[1];
|
|
4083
|
+
// 工具面瘦身统计(state.slim = 按已保存配置算的 {total, main},ConfigPanel
|
|
4084
|
+
// 的 load / doRescan / doApply 经 props.setSlimInfo 回写):副标题下一行,
|
|
4085
|
+
// 版本号 / 更新卡同区呈现。null = 未取到 / 老服务端无字段,整行零占位。
|
|
4086
|
+
var _sl = useState(null), slimInfo = _sl[0], setSlimInfo = _sl[1];
|
|
4087
|
+
// 委派效果统计(GET stats,页头瘦身行之下):懒加载——本 effect 在首屏
|
|
4088
|
+
// 渲染完成后才发请求,不阻塞开面板(state 全量检测照旧先行)。只读展示
|
|
4089
|
+
// 拉一次不重试;服务缺失 / 拉取失败 / 响应异常一律维持 null 整卡不渲染,
|
|
4090
|
+
// 不为锦上添花的统计打扰使用。卸载后丢弃在途响应防置迟数据。
|
|
4091
|
+
var _st = useState(null), delStats = _st[0], setDelStats = _st[1];
|
|
4092
|
+
useEffect(function () {
|
|
4093
|
+
var alive = true;
|
|
4094
|
+
paodingApi("stats").then(function (d) {
|
|
4095
|
+
if (alive && d && d.ok === true) setDelStats(d);
|
|
4096
|
+
}).catch(function () { /* 静默降级:整卡不渲染 */ });
|
|
4097
|
+
return function () { alive = false; };
|
|
4098
|
+
}, []);
|
|
3766
4099
|
// 语言切换 tick:页头文案(标题 / 副标题 / 菜单 / 语言项)走 t(),切换后
|
|
3767
4100
|
// 需整体重渲染一次;ConfigPanel 内部另有 registerReload 整页重拉。
|
|
3768
4101
|
var _lg = useState(0), langTick = _lg[0], setLangTick = _lg[1];
|
|
@@ -3853,6 +4186,127 @@ window.__ModuleLoader__.load({
|
|
|
3853
4186
|
});
|
|
3854
4187
|
}
|
|
3855
4188
|
|
|
4189
|
+
// ── 恢复备份:备份列表弹层 ────────────────────────────────────────────
|
|
4190
|
+
// 打开即拉 GET config/backups(服务端已按新→旧排好,客户端不再排序);
|
|
4191
|
+
// 行点击走 confirmCtrl 二次确认(覆盖前当前配置会先自动备份,danger 红钮
|
|
4192
|
+
// 与导入同款门槛),确认后 POST config/restore——成功 toast + 面板既有
|
|
4193
|
+
// 刷新路径(reloadRef 重拉 state 重建 assign,预览 / 提示卡一并作废,
|
|
4194
|
+
// 委派行显示名跟着重注册);409 / 422 / 404 按既有错误 toast 惯例呈现
|
|
4195
|
+
//(本地前缀 + 服务端 message 原文),弹层留着方便换一个备份再试。
|
|
4196
|
+
var _bko = useState(false), bkOpen = _bko[0], setBkOpen = _bko[1];
|
|
4197
|
+
var _bkl = useState([]), bkList = _bkl[0], setBkList = _bkl[1];
|
|
4198
|
+
// bkBusy 双语义:列表拉取中(弹层显示加载行)/ 恢复请求在途(行全禁用,
|
|
4199
|
+
// 防双击重复回滚)。
|
|
4200
|
+
var _bkb = useState(false), bkBusy = _bkb[0], setBkBusy = _bkb[1];
|
|
4201
|
+
var _bke = useState(""), bkErr = _bke[0], setBkErr = _bke[1];
|
|
4202
|
+
|
|
4203
|
+
function openBackupModal() {
|
|
4204
|
+
setMoreOpen(false);
|
|
4205
|
+
setBkOpen(true);
|
|
4206
|
+
setBkList([]);
|
|
4207
|
+
setBkErr("");
|
|
4208
|
+
setBkBusy(true);
|
|
4209
|
+
paodingApi("config/backups").then(function (d) {
|
|
4210
|
+
if (!d || d.ok !== true || !Array.isArray(d.backups)) throw new Error(t("menu.backupBadResponse"));
|
|
4211
|
+
setBkList(d.backups);
|
|
4212
|
+
}).catch(function (err) {
|
|
4213
|
+
setBkErr(t("menu.backupListFail", { msg: err.message }));
|
|
4214
|
+
}).finally(function () {
|
|
4215
|
+
setBkBusy(false);
|
|
4216
|
+
});
|
|
4217
|
+
}
|
|
4218
|
+
|
|
4219
|
+
// 关弹层:恢复请求在途时不收(请求自见分晓,中途关层容易误以为没生效)。
|
|
4220
|
+
function closeBackupModal() {
|
|
4221
|
+
if (bkBusy) return;
|
|
4222
|
+
setBkOpen(false);
|
|
4223
|
+
}
|
|
4224
|
+
|
|
4225
|
+
function pickBackup(item) {
|
|
4226
|
+
if (!item || typeof item.file !== "string" || item.file === "" || bkBusy) return;
|
|
4227
|
+
confirmCtrl.ask({
|
|
4228
|
+
title: t("menu.restoreTitle"),
|
|
4229
|
+
body: t("menu.restoreBody", { name: backupTagOf(item.file) }),
|
|
4230
|
+
danger: true,
|
|
4231
|
+
confirmText: t("menu.restoreConfirm"),
|
|
4232
|
+
onConfirm: function () {
|
|
4233
|
+
setBkBusy(true);
|
|
4234
|
+
paodingApi("config/restore", { file: item.file }).then(function () {
|
|
4235
|
+
toast.ok(t("menu.restored", { name: backupTagOf(item.file) }));
|
|
4236
|
+
setBkOpen(false);
|
|
4237
|
+
// 面板既有刷新路径(与「导入配置」同款):重拉 state 重建 assign;
|
|
4238
|
+
// 显示名 / 角色名单可能被回滚改变,事件流委派行键控注册跟着刷新。
|
|
4239
|
+
if (typeof reloadRef.current === "function") reloadRef.current();
|
|
4240
|
+
refreshDelegateRows();
|
|
4241
|
+
}).catch(function (err) {
|
|
4242
|
+
toast.err(t("menu.restoreFail", { msg: err.message }));
|
|
4243
|
+
}).finally(function () {
|
|
4244
|
+
setBkBusy(false);
|
|
4245
|
+
});
|
|
4246
|
+
},
|
|
4247
|
+
});
|
|
4248
|
+
}
|
|
4249
|
+
|
|
4250
|
+
// 备份弹层开着时的 Esc 分层(捕获级,与 ConfirmLayer 同款思路):只收
|
|
4251
|
+
// 弹层本身;二次确认框开着时放行——ConfirmLayer 的监听(注册在前)先
|
|
4252
|
+
// stopPropagation 并只关确认框,这里不重复收弹层。
|
|
4253
|
+
useEffect(function () {
|
|
4254
|
+
if (!bkOpen) return;
|
|
4255
|
+
var onKey = function (e) {
|
|
4256
|
+
if (!e || e.key !== "Escape") return;
|
|
4257
|
+
if (confirmCtrl.getSnapshot().req) return; // 确认框优先:让它自己的监听去收
|
|
4258
|
+
e.stopPropagation();
|
|
4259
|
+
closeBackupModal();
|
|
4260
|
+
};
|
|
4261
|
+
document.addEventListener("keydown", onKey, true);
|
|
4262
|
+
return function () { document.removeEventListener("keydown", onKey, true); };
|
|
4263
|
+
}, [bkOpen, bkBusy]);
|
|
4264
|
+
|
|
4265
|
+
// 备份列表弹层(结构对齐 confirmModalOf 的 pd-modal* 家族):整行可点
|
|
4266
|
+
// 按钮列表(bak 徽标 + 本地时间 + 大小,服务端已按新→旧排好);拉取中
|
|
4267
|
+
// 一行加载文案、空列表「暂无备份」、失败一行原因(不弹 toast,弹层里
|
|
4268
|
+
// 就地交代)。body 高度封顶出滚,备份链再长也不顶破视口。
|
|
4269
|
+
function backupModalOf() {
|
|
4270
|
+
if (!bkOpen) return null;
|
|
4271
|
+
var rows = bkList.map(function (it, i) {
|
|
4272
|
+
return h("button", {
|
|
4273
|
+
type: "button",
|
|
4274
|
+
key: it && typeof it.file === "string" && it.file ? it.file : "bak-" + i,
|
|
4275
|
+
className: "pd-backupItem",
|
|
4276
|
+
disabled: bkBusy,
|
|
4277
|
+
title: it && typeof it.file === "string" ? it.file : undefined,
|
|
4278
|
+
onClick: function () { pickBackup(it); },
|
|
4279
|
+
},
|
|
4280
|
+
h("span", { className: "pd-backupTag" }, backupTagOf(it && it.file)),
|
|
4281
|
+
h("span", { className: "pd-backupTime" }, backupTimeText(it && it.mtimeMs)),
|
|
4282
|
+
h("span", { className: "pd-backupSize" }, backupSizeText(it && it.size)));
|
|
4283
|
+
});
|
|
4284
|
+
return h("div", {
|
|
4285
|
+
className: "pd-modalMask",
|
|
4286
|
+
// 只点遮罩空白处才收(与 confirmModalOf 同款 mousedown 判据)
|
|
4287
|
+
onMouseDown: function (e) { if (e.target === e.currentTarget) closeBackupModal(); },
|
|
4288
|
+
},
|
|
4289
|
+
h("div", { className: "pd-modalCard", role: "dialog", "aria-modal": "true", "aria-label": t("menu.backupTitle") },
|
|
4290
|
+
h("div", { className: "pd-modalHeader" },
|
|
4291
|
+
h("h2", { className: "pd-modalTitle" }, t("menu.backupTitle")),
|
|
4292
|
+
h("button", {
|
|
4293
|
+
type: "button", className: "pd-modalClose", "aria-label": t("common.close"),
|
|
4294
|
+
onClick: closeBackupModal,
|
|
4295
|
+
}, h("span", { className: "pd-closeGlyph", "aria-hidden": "true" }, "×"))),
|
|
4296
|
+
h("div", { className: "pd-modalBody" },
|
|
4297
|
+
bkErr
|
|
4298
|
+
? h("div", { className: "omd-status omd-statusError" },
|
|
4299
|
+
h("span", { className: "omd-statusIcon" }, "⛔"),
|
|
4300
|
+
h("span", { className: "omd-statusText" }, bkErr))
|
|
4301
|
+
: bkBusy && !bkList.length
|
|
4302
|
+
? h("div", { className: "pd-backupEmpty" }, t("menu.backupLoading"))
|
|
4303
|
+
: bkList.length
|
|
4304
|
+
? h("div", { className: "pd-backupList" }, rows)
|
|
4305
|
+
: h("div", { className: "pd-backupEmpty" }, t("menu.backupEmpty"))),
|
|
4306
|
+
h("div", { className: "pd-modalFooter" },
|
|
4307
|
+
h(P.Button, { variant: "outline", size: "md", disabled: bkBusy, onClick: closeBackupModal }, t("common.close")))));
|
|
4308
|
+
}
|
|
4309
|
+
|
|
3856
4310
|
// 菜单开着时 Esc 只收菜单(捕获级先于 PageShell 的关页监听,与
|
|
3857
4311
|
// ConfigPanel 弹窗的 Esc 分层同款);点菜单外空白处收菜单。
|
|
3858
4312
|
useEffect(function () {
|
|
@@ -3915,7 +4369,13 @@ window.__ModuleLoader__.load({
|
|
|
3915
4369
|
: null),
|
|
3916
4370
|
versionRowOf(verInfo, { busy: upgBusy, onUpgrade: doUpgrade, note: upgNote, error: upgErr, onRetry: doRetryVersion, retryBusy: verRetryBusy })),
|
|
3917
4371
|
h("p", { className: "pd-pageSubtitle" },
|
|
3918
|
-
t("menu.subtitle"))
|
|
4372
|
+
t("menu.subtitle")),
|
|
4373
|
+
// 工具面瘦身统计一行:按已保存的全局配置(state.slim,见 setSlimInfo
|
|
4374
|
+
// 回写点);数据未就绪 / 老服务端无该字段时零占位。
|
|
4375
|
+
slimInfo ? h("p", { className: "pd-slimLine" }, slimLineText(slimInfo)) : null,
|
|
4376
|
+
// 委派效果卡:紧贴瘦身行之下(delegationCardOf 自判,统计没拉到 /
|
|
4377
|
+
// 服务缺失时返回 null 整卡不渲染)。
|
|
4378
|
+
delegationCardOf(delStats)),
|
|
3919
4379
|
// 页头「更多」菜单:整份配置文件的导出 / 导入(低频操作收进来,页头
|
|
3920
4380
|
// 不添第二排按钮)。定位锚点用相对定位的薄包裹层,下拉复用工作区
|
|
3921
4381
|
// 「更多」的 omd-tabMenu / omd-tabMenuItem 既有样式;靠右对齐(inline
|
|
@@ -3947,6 +4407,13 @@ window.__ModuleLoader__.load({
|
|
|
3947
4407
|
disabled: expBusy || impBusy,
|
|
3948
4408
|
onClick: pickImportFile,
|
|
3949
4409
|
}, h("span", { className: "omd-tabText" }, impBusy ? t("menu.importing") : t("menu.importItem"))),
|
|
4410
|
+
// 恢复备份:弹备份列表(行点选 → confirmCtrl 二次确认 → POST
|
|
4411
|
+
// restore,见 openBackupModal / pickBackup / backupModalOf)。
|
|
4412
|
+
h("button", {
|
|
4413
|
+
type: "button", role: "menuitem", className: "omd-tabMenuItem",
|
|
4414
|
+
disabled: expBusy || impBusy,
|
|
4415
|
+
onClick: openBackupModal,
|
|
4416
|
+
}, h("span", { className: "omd-tabText" }, t("menu.restoreItem"))),
|
|
3950
4417
|
// 语言切换项:label 刻意不进字典——按当前语言显示「另一端」的叫法,
|
|
3951
4418
|
// 中文界面显示 English,英文界面显示中文(语言名用母语写才认得出)。
|
|
3952
4419
|
h("button", {
|
|
@@ -3974,7 +4441,11 @@ window.__ModuleLoader__.load({
|
|
|
3974
4441
|
title: t("nav.closeTip"),
|
|
3975
4442
|
onClick: function () { if (typeof onClose === "function") onClose(); },
|
|
3976
4443
|
}, h("span", { className: "pd-closeGlyph", "aria-hidden": "true" }, "×"))),
|
|
3977
|
-
h(ConfigPanel, { setVerInfo: setVerInfo, registerReload: registerReload }),
|
|
4444
|
+
h(ConfigPanel, { setVerInfo: setVerInfo, setSlimInfo: setSlimInfo, registerReload: registerReload }),
|
|
4445
|
+
// 恢复备份弹层:fixed 遮罩不占布局流;置于 ConfirmLayer 之前——两者
|
|
4446
|
+
// 遮罩同为 z-60,按 DOM 顺序叠放,行点选后的二次确认框必须盖在列表
|
|
4447
|
+
// 弹层之上。
|
|
4448
|
+
backupModalOf(),
|
|
3978
4449
|
// 反馈 toast 宿主:订阅模块级 toast 控制器,悬浮于操作坞上方
|
|
3979
4450
|
h(ToastHost),
|
|
3980
4451
|
// 通用确认弹窗层:订阅 confirmCtrl(发起方见各 confirmCtrl.ask 调用点),
|