sage-mem 1.8.0 → 1.9.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/README.md +5 -1
- package/README.zh.md +5 -1
- package/icon.png +0 -0
- package/lib/client.js +738 -175
- package/package.json +4 -2
package/lib/client.js
CHANGED
|
@@ -2231,6 +2231,16 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
2231
2231
|
".smem-page-top{display:flex;align-items:center;}",
|
|
2232
2232
|
".smem-page-back{display:inline-flex;align-items:center;gap:5px;border:0;background:transparent;color:var(--dsw-alias-label-secondary);font-family:inherit;font-size:12.5px;line-height:1.4;padding:3px 6px 3px 2px;border-radius:6px;cursor:pointer;}",
|
|
2233
2233
|
".smem-page-back:hover{color:var(--dsw-alias-label-primary,#1f2329);background:var(--dsw-alias-bg-layer-2);}",
|
|
2234
|
+
// ── 1.8.2:插件详情页的「侧栏入口」卡片 ──
|
|
2235
|
+
// 融入宿主插件页:标题 13px/600、说明 12px 次要色,圆角与分隔线只用宿主 token。
|
|
2236
|
+
// 不抄设置页的大标题 —— 这一块是插件页里的一张紧凑卡片,不是另一个 settings section。
|
|
2237
|
+
".smem-plugin-card{display:flex;flex-direction:column;gap:8px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:12px 14px;background:var(--dsw-alias-bg-layer-1,#fafbfc);box-sizing:border-box;}",
|
|
2238
|
+
".smem-plugin-card-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}",
|
|
2239
|
+
".smem-plugin-card-title{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary,#1f2430);line-height:1.4;}",
|
|
2240
|
+
".smem-plugin-card-status{font-size:12px;color:var(--dsw-alias-label-secondary,#6b7280);line-height:1.4;}",
|
|
2241
|
+
".smem-plugin-card-check{font-size:12.5px;}",
|
|
2242
|
+
".smem-plugin-card-note{font-size:12px;line-height:1.55;color:var(--dsw-alias-label-secondary,#6b7280);}",
|
|
2243
|
+
".smem-plugin-card-err{font-size:12px;line-height:1.5;color:var(--dsw-alias-state-error-primary,#ef4444);overflow-wrap:anywhere;}",
|
|
2234
2244
|
// ── 底部一行仓库链接(引流,不抢视线)──
|
|
2235
2245
|
".smem-star{display:flex;align-items:center;justify-content:flex-end;gap:8px;flex-wrap:wrap;font-size:12px;color:var(--dsw-alias-label-tertiary,#8b93a1);padding:2px 2px 0;}",
|
|
2236
2246
|
".smem-star-link{color:var(--dsw-alias-brand-primary,#4d76e6);text-decoration:underline;}",
|
|
@@ -2499,7 +2509,7 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
2499
2509
|
const isOpen = !!props.expanded;
|
|
2500
2510
|
return h("div", { className: "smem-card", key: item.file }, [
|
|
2501
2511
|
h("div", { className: "smem-card-head", key: "head" }, [
|
|
2502
|
-
h("span", { className: "smem-badge", key: "badge", title: "
|
|
2512
|
+
h("span", { className: "smem-badge", key: "badge", title: t("card.typeTitle", { type: String(item.type) }) }, meta[0] + " " + typeLabel(TYPE_META[item.type] ? item.type : "reference")),
|
|
2503
2513
|
h("span", { className: "smem-card-title", key: "title" }, item.file),
|
|
2504
2514
|
h("span", { className: "smem-muted", key: "size" }, fmtSize(item.size)),
|
|
2505
2515
|
]),
|
|
@@ -2514,37 +2524,36 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
2514
2524
|
"data-clamp": isOpen ? "0" : "1",
|
|
2515
2525
|
key: "body",
|
|
2516
2526
|
onClick: function () { props.onToggle(item.file); },
|
|
2517
|
-
title: isOpen ? "
|
|
2527
|
+
title: isOpen ? t("card.collapse") : t("card.expand"),
|
|
2518
2528
|
}, String(item.description))
|
|
2519
2529
|
: null,
|
|
2520
2530
|
h("div", { className: "smem-card-foot", key: "foot" },
|
|
2521
2531
|
props.archiving
|
|
2522
2532
|
? h("div", { className: "smem-ar-confirm", key: "ar" }, [
|
|
2523
|
-
h("div", { key: "what" }, "
|
|
2533
|
+
h("div", { key: "what" }, t("card.archiveWhat", { file: item.file })),
|
|
2524
2534
|
// 「可留空」必须跟输入框贴在一起、并且独立成行 —— 它是这一块最容易
|
|
2525
2535
|
// 被误读成「必须填才能按」的地方。
|
|
2526
|
-
h("label", { className: "smem-label", key: "lb" }, "
|
|
2536
|
+
h("label", { className: "smem-label", key: "lb" }, t("card.archiveReason")),
|
|
2527
2537
|
h("input", {
|
|
2528
2538
|
className: "smem-input", key: "in",
|
|
2529
2539
|
value: props.archiveReason || "",
|
|
2530
|
-
placeholder: "
|
|
2540
|
+
placeholder: t("card.archiveReasonPlaceholder"),
|
|
2531
2541
|
onChange: function (e) { props.onArchiveReason(e.target.value); },
|
|
2532
2542
|
}),
|
|
2533
|
-
h("div", { className: "smem-ar-hint", key: "hint" },
|
|
2534
|
-
"写下当时的判断,以后翻到它时就知道这条还值不值得捞回来。"),
|
|
2543
|
+
h("div", { className: "smem-ar-hint", key: "hint" }, t("card.archiveHint")),
|
|
2535
2544
|
h("div", { className: "smem-row", key: "btns" }, [
|
|
2536
|
-
h("button", { type: "button", className: "smem-btn smem-btn-primary", key: "ok", disabled: props.busy, onClick: function () { props.onConfirmArchive(item.file); } }, "
|
|
2537
|
-
h("button", { type: "button", className: "smem-btn", key: "no", disabled: props.busy, onClick: props.onCancelArchive }, "
|
|
2545
|
+
h("button", { type: "button", className: "smem-btn smem-btn-primary", key: "ok", disabled: props.busy, onClick: function () { props.onConfirmArchive(item.file); } }, t("card.confirmArchive")),
|
|
2546
|
+
h("button", { type: "button", className: "smem-btn", key: "no", disabled: props.busy, onClick: props.onCancelArchive }, t("card.cancel")),
|
|
2538
2547
|
]),
|
|
2539
2548
|
])
|
|
2540
2549
|
: h("div", { className: "smem-actions", key: "acts" },
|
|
2541
2550
|
props.deleting
|
|
2542
2551
|
? [
|
|
2543
|
-
h("button", { type: "button", className: "smem-btn smem-btn-danger", key: "yes", onClick: function () { props.onConfirmDelete(item.file); } }, "
|
|
2544
|
-
h("button", { type: "button", className: "smem-btn", key: "no", onClick: function () { props.onCancelDelete(); } }, "
|
|
2552
|
+
h("button", { type: "button", className: "smem-btn smem-btn-danger", key: "yes", onClick: function () { props.onConfirmDelete(item.file); } }, t("card.confirmDelete")),
|
|
2553
|
+
h("button", { type: "button", className: "smem-btn", key: "no", onClick: function () { props.onCancelDelete(); } }, t("card.cancel")),
|
|
2545
2554
|
]
|
|
2546
2555
|
: [
|
|
2547
|
-
h("button", { type: "button", className: "smem-btn", key: "edit", onClick: function () { props.onEdit(item.file); } }, "
|
|
2556
|
+
h("button", { type: "button", className: "smem-btn", key: "edit", onClick: function () { props.onEdit(item.file); } }, t("card.edit")),
|
|
2548
2557
|
// 语义分组:左边只放「编辑」(安全、可逆),右边放「锁定 / 归档 / 删除」(对这条记忆的退役/保护决定)。
|
|
2549
2558
|
h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
2550
2559
|
/**
|
|
@@ -2560,21 +2569,21 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
2560
2569
|
className: "smem-btn" + (item.locked ? " smem-btn-locked" : ""),
|
|
2561
2570
|
key: "lock",
|
|
2562
2571
|
title: item.locked
|
|
2563
|
-
? "
|
|
2564
|
-
: "
|
|
2572
|
+
? t("card.lockedTitle")
|
|
2573
|
+
: t("card.unlockedTitle"),
|
|
2565
2574
|
disabled: props.busy,
|
|
2566
2575
|
onClick: function () { props.onToggleLock(item.file, !item.locked); },
|
|
2567
|
-
}, item.locked ? "
|
|
2576
|
+
}, item.locked ? t("card.locked") : t("card.lock")),
|
|
2568
2577
|
// 归档排在「锁定」和「删除」之间:比编辑轻、比删除温和,且随时可逆。
|
|
2569
2578
|
h("button", {
|
|
2570
2579
|
type: "button", className: "smem-btn", key: "arch",
|
|
2571
2580
|
title: item.locked
|
|
2572
|
-
? "
|
|
2573
|
-
: "
|
|
2581
|
+
? t("card.archiveLockedTitle")
|
|
2582
|
+
: t("card.archiveTitle"),
|
|
2574
2583
|
disabled: props.busy || item.locked === true,
|
|
2575
2584
|
onClick: function () { props.onAskArchive(item.file); },
|
|
2576
|
-
}, "
|
|
2577
|
-
h("button", { type: "button", className: "smem-btn smem-btn-danger-ghost", key: "del", onClick: function () { props.onAskDelete(item.file); } }, "
|
|
2585
|
+
}, t("card.archive")),
|
|
2586
|
+
h("button", { type: "button", className: "smem-btn smem-btn-danger-ghost", key: "del", onClick: function () { props.onAskDelete(item.file); } }, t("card.delete")),
|
|
2578
2587
|
])),
|
|
2579
2588
|
]);
|
|
2580
2589
|
}
|
|
@@ -2602,9 +2611,9 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
2602
2611
|
const descOpen = sDescOpen[0], setDescOpen = sDescOpen[1];
|
|
2603
2612
|
return h("div", { className: "smem-card smem-card--archived", key: item.file }, [
|
|
2604
2613
|
h("div", { className: "smem-card-head", key: "head" }, [
|
|
2605
|
-
h("span", { className: "smem-badge", key: "badge", title: "
|
|
2614
|
+
h("span", { className: "smem-badge", key: "badge", title: t("card.typeTitle", { type: String(item.type) }) }, meta[0] + " " + typeLabel(TYPE_META[item.type] ? item.type : "reference")),
|
|
2606
2615
|
h("span", { className: "smem-card-title", key: "title" }, item.file),
|
|
2607
|
-
h("span", { className: "smem-ar-meta", key: "at", title: "
|
|
2616
|
+
h("span", { className: "smem-ar-meta", key: "at", title: t("card.archivedAtTitle") }, t("card.archivedAt", { at: item.archivedAt || t("card.unknownTime") })),
|
|
2608
2617
|
h("span", { className: "smem-muted", key: "size" }, fmtSize(item.size)),
|
|
2609
2618
|
]),
|
|
2610
2619
|
(item.tags && item.tags.length)
|
|
@@ -2618,21 +2627,20 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
2618
2627
|
? h("div", {
|
|
2619
2628
|
className: "smem-narrative", key: "desc",
|
|
2620
2629
|
"data-clamp": descOpen ? "0" : "1",
|
|
2621
|
-
title: descOpen ? "
|
|
2630
|
+
title: descOpen ? t("card.collapse") : t("card.expand"),
|
|
2622
2631
|
onClick: function () { setDescOpen(!descOpen); },
|
|
2623
2632
|
}, String(item.description))
|
|
2624
2633
|
: null,
|
|
2625
2634
|
h("div", {
|
|
2626
2635
|
className: "smem-ar-reason smem-clamp-2" + (descOpen ? " smem-clamp-2--open" : ""),
|
|
2627
|
-
key: "why", title: "
|
|
2636
|
+
key: "why", title: t("card.reason", { reason: item.archivedReason || t("card.noReason") }),
|
|
2628
2637
|
onClick: function () { setDescOpen(!descOpen); },
|
|
2629
|
-
}, "
|
|
2638
|
+
}, t("card.reason", { reason: item.archivedReason || t("card.noReason") })),
|
|
2630
2639
|
props.editing
|
|
2631
2640
|
? h("div", { className: "smem-ar-edit", key: "edit" }, [
|
|
2632
|
-
h("div", { className: "smem-ar-hint", key: "note" },
|
|
2633
|
-
"原样文本编辑:下面就是这份文件的全文(含 frontmatter 与归档留痕),保存时原样写回,不会被重新生成。"),
|
|
2641
|
+
h("div", { className: "smem-ar-hint", key: "note" }, t("card.rawEditNote")),
|
|
2634
2642
|
props.loading
|
|
2635
|
-
? h("div", { className: "smem-status", key: "loading" }, "
|
|
2643
|
+
? h("div", { className: "smem-status", key: "loading" }, t("card.reading"))
|
|
2636
2644
|
: h("textarea", {
|
|
2637
2645
|
className: "smem-textarea smem-ar-editor", key: "ta",
|
|
2638
2646
|
value: props.text || "",
|
|
@@ -2640,20 +2648,17 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
2640
2648
|
onChange: function (e) { props.onText(e.target.value); },
|
|
2641
2649
|
}),
|
|
2642
2650
|
h("div", { className: "smem-row", key: "btns" }, [
|
|
2643
|
-
h("button", { type: "button", className: "smem-btn smem-btn-primary", key: "save", disabled: props.busy || props.loading, onClick: function () { props.onSave(item.file); } }, props.saving ? "
|
|
2644
|
-
h("button", { type: "button", className: "smem-btn", key: "cancel", disabled: props.busy, onClick: props.onCancel }, "
|
|
2651
|
+
h("button", { type: "button", className: "smem-btn smem-btn-primary", key: "save", disabled: props.busy || props.loading, onClick: function () { props.onSave(item.file); } }, props.saving ? t("card.saving") : t("card.save")),
|
|
2652
|
+
h("button", { type: "button", className: "smem-btn", key: "cancel", disabled: props.busy, onClick: props.onCancel }, t("card.cancel")),
|
|
2645
2653
|
]),
|
|
2646
2654
|
])
|
|
2647
2655
|
: props.deleting
|
|
2648
2656
|
? h("div", { className: "smem-ar-confirm", key: "del" }, [
|
|
2649
|
-
h("div", { key: "what" },
|
|
2650
|
-
|
|
2651
|
-
"要保留它就先点「恢复」,把它放回活动记忆。"),
|
|
2652
|
-
h("div", { className: "smem-ar-hint", key: "hint" },
|
|
2653
|
-
"这一步之后没有任何界面能把内容找回来 —— 只剩你自己在磁盘上留过备份。"),
|
|
2657
|
+
h("div", { key: "what" }, t("card.purgeWhat", { file: item.file })),
|
|
2658
|
+
h("div", { className: "smem-ar-hint", key: "hint" }, t("card.purgeHint")),
|
|
2654
2659
|
h("div", { className: "smem-row", key: "btns" }, [
|
|
2655
|
-
h("button", { type: "button", className: "smem-btn smem-btn-danger", key: "yes", disabled: props.busy, onClick: function () { props.onConfirmDelete(item.file); } }, "
|
|
2656
|
-
h("button", { type: "button", className: "smem-btn", key: "no", disabled: props.busy, onClick: props.onCancelDelete }, "
|
|
2660
|
+
h("button", { type: "button", className: "smem-btn smem-btn-danger", key: "yes", disabled: props.busy, onClick: function () { props.onConfirmDelete(item.file); } }, t("card.confirmPurge")),
|
|
2661
|
+
h("button", { type: "button", className: "smem-btn", key: "no", disabled: props.busy, onClick: props.onCancelDelete }, t("card.cancel")),
|
|
2657
2662
|
]),
|
|
2658
2663
|
])
|
|
2659
2664
|
: h("div", { className: "smem-card-foot", key: "foot" },
|
|
@@ -2662,21 +2667,21 @@ const StarmapRuntime = (function (require, _react) {
|
|
|
2662
2667
|
type: "button", className: "smem-btn", key: "edit",
|
|
2663
2668
|
disabled: props.busy,
|
|
2664
2669
|
onClick: function () { props.onEdit(item.file); },
|
|
2665
|
-
}, "
|
|
2670
|
+
}, t("card.edit")),
|
|
2666
2671
|
h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
2667
2672
|
h("button", {
|
|
2668
2673
|
type: "button", className: "smem-btn smem-btn-primary", key: "restore",
|
|
2669
2674
|
disabled: props.busy,
|
|
2670
2675
|
onClick: function () { props.onRestore(item.file); },
|
|
2671
|
-
}, props.restoring ? "
|
|
2676
|
+
}, props.restoring ? t("card.restoring") : t("card.restore")),
|
|
2672
2677
|
// 放在最后、且用 danger-ghost:它是这张卡上唯一不可逆的动作,
|
|
2673
2678
|
// 不该跟「恢复」长得一样。
|
|
2674
2679
|
h("button", {
|
|
2675
2680
|
type: "button", className: "smem-btn smem-btn-danger-ghost", key: "purge",
|
|
2676
2681
|
disabled: props.busy,
|
|
2677
|
-
title: "
|
|
2682
|
+
title: t("card.purgeTitle"),
|
|
2678
2683
|
onClick: function () { props.onAskDelete(item.file); },
|
|
2679
|
-
}, "
|
|
2684
|
+
}, t("card.purge")),
|
|
2680
2685
|
])),
|
|
2681
2686
|
]);
|
|
2682
2687
|
}
|
|
@@ -2693,9 +2698,6 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
2693
2698
|
};
|
|
2694
2699
|
const autoArchiveOf = (v) => (v === "off" || v === "auto" ? v : "report");
|
|
2695
2700
|
|
|
2696
|
-
/** 头部带上的「我在哪」:五个 tab 的中文名(与 .smem-tabs 上的字面一致)。 */
|
|
2697
|
-
const TAB_LABEL = { files: "文件列表", star: "记忆星图", autodream: "自动做梦", archive: "自动归档", audit: "体检" };
|
|
2698
|
-
|
|
2699
2701
|
/**
|
|
2700
2702
|
* 工具面板头部的「收起」按钮:抽屉里开的工具要能一键收回去 ——
|
|
2701
2703
|
* 面板不收,列表就还在下面被顶着。
|
|
@@ -2841,14 +2843,14 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
2841
2843
|
*/
|
|
2842
2844
|
function StarLine() {
|
|
2843
2845
|
return h("div", { className: "smem-star" }, [
|
|
2844
|
-
h("span", { key: "ask" }, "
|
|
2846
|
+
h("span", { key: "ask" }, t("star.ask")),
|
|
2845
2847
|
h("a", {
|
|
2846
2848
|
key: "cta",
|
|
2847
2849
|
href: "https://github.com/gezi-wen/sage-mem",
|
|
2848
2850
|
target: "_blank",
|
|
2849
2851
|
rel: "noreferrer",
|
|
2850
2852
|
className: "smem-star-link",
|
|
2851
|
-
}, "
|
|
2853
|
+
}, t("star.cta")),
|
|
2852
2854
|
]);
|
|
2853
2855
|
}
|
|
2854
2856
|
|
|
@@ -3054,6 +3056,440 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
3054
3056
|
return { provider: "", model: "" };
|
|
3055
3057
|
}
|
|
3056
3058
|
|
|
3059
|
+
// ── i18n pilot: settings.memory ─────────────────────────────────────
|
|
3060
|
+
// The `zh` values below are copied verbatim from the strings this shard
|
|
3061
|
+
// replaces; they must render byte-identically to the pre-i18n client.
|
|
3062
|
+
// The `en` draft is a complete key-for-key translation. Placeholders use
|
|
3063
|
+
// the host locale service's own syntax (`{name}`), so a bound `t` and the
|
|
3064
|
+
// local fallback below substitute the same way.
|
|
3065
|
+
const NS = "settings.memory";
|
|
3066
|
+
|
|
3067
|
+
const ZH = {
|
|
3068
|
+
"section.label": "记忆管理",
|
|
3069
|
+
"page.back": "返回会话",
|
|
3070
|
+
"sidebar.label": "记忆管理",
|
|
3071
|
+
|
|
3072
|
+
"tab.files": "文件列表",
|
|
3073
|
+
"tab.star": "记忆星图",
|
|
3074
|
+
"tab.autodream": "自动做梦",
|
|
3075
|
+
"tab.archive": "自动归档",
|
|
3076
|
+
"tab.audit": "体检",
|
|
3077
|
+
|
|
3078
|
+
"type.user": "用户",
|
|
3079
|
+
"type.feedback": "反馈",
|
|
3080
|
+
"type.project": "项目",
|
|
3081
|
+
"type.reference": "参考",
|
|
3082
|
+
"type.special": "特殊",
|
|
3083
|
+
|
|
3084
|
+
"header.dirReadable": "memory 目录可读",
|
|
3085
|
+
"header.dirUnreadable": "memory 目录不可读",
|
|
3086
|
+
"header.dirTitle": "{state} · {tab}",
|
|
3087
|
+
"header.counts": "活动中 {active} · 已归档 {archived}",
|
|
3088
|
+
"header.countsFiltered": "筛出 {shown} / {total} · 活动中 {active} · 已归档 {archived}",
|
|
3089
|
+
"header.auditSwitch": "切到体检看是哪几条",
|
|
3090
|
+
"header.auditClean": "体检没有发现问题",
|
|
3091
|
+
"header.auditRun": "跑一次体检(只读)",
|
|
3092
|
+
"header.auditProblems": "体检 {count} 个问题",
|
|
3093
|
+
"header.auditPass": "体检 通过",
|
|
3094
|
+
"header.auditNotRun": "体检 · 未跑",
|
|
3095
|
+
"header.restartTitle": "有改动要重启 DSH 才生效",
|
|
3096
|
+
"header.restartPending": "待重启生效",
|
|
3097
|
+
"header.restartPendingCount": "{count} 项待重启生效",
|
|
3098
|
+
"header.openFolderTitle": "在系统文件管理器里打开记忆目录",
|
|
3099
|
+
"header.openFolder": "打开记忆文件夹",
|
|
3100
|
+
"header.refresh": "刷新",
|
|
3101
|
+
"action.add": "+ 添加记忆",
|
|
3102
|
+
|
|
3103
|
+
"form.addTitle": "添加记忆",
|
|
3104
|
+
"form.editTitle": "编辑 {file}",
|
|
3105
|
+
"form.type": "类型:",
|
|
3106
|
+
"form.fileName": "文件名(.md,留空自动 {type}_note.md)",
|
|
3107
|
+
"form.fileNamePlaceholder": "例如 project_example.md",
|
|
3108
|
+
"form.desc": "描述(写进 frontmatter,检索与列表都靠它)",
|
|
3109
|
+
"form.descPlaceholder": "一句话说清这条记忆是什么",
|
|
3110
|
+
"form.tags": "自定义标签(可选,逗号分隔;会写进 frontmatter 的 tags,列表页可按它筛)",
|
|
3111
|
+
"form.tagsPlaceholder": "例如 部署, 笔记, dsh",
|
|
3112
|
+
"form.body": "正文(可选,frontmatter 自动生成)",
|
|
3113
|
+
"form.bodyPlaceholder": "要长期记住的细节",
|
|
3114
|
+
|
|
3115
|
+
"update.found": "发现新版本 v{latest}(当前 v{current})",
|
|
3116
|
+
"update.notes": "看更新内容",
|
|
3117
|
+
"update.dismissTitle": "这次不看了(下次进面板还会提示)",
|
|
3118
|
+
|
|
3119
|
+
"toolbar.search": "搜索文件名 / 描述 / 类型 / 标签…",
|
|
3120
|
+
"tools.button": "工具 ▾",
|
|
3121
|
+
"tools.title": "归档候选 / 保留名编辑",
|
|
3122
|
+
"tools.titleDebug": "归档候选 / 保留名编辑 / 注入调试",
|
|
3123
|
+
"tools.candidates": "归档候选",
|
|
3124
|
+
"tools.candidatesCount": "{count} 条",
|
|
3125
|
+
"tools.candidatesEmpty": "未取",
|
|
3126
|
+
"tools.raw": "保留名编辑",
|
|
3127
|
+
"tools.rawCount": "{count} 个",
|
|
3128
|
+
"tools.debug": "注入调试",
|
|
3129
|
+
"tools.debugCount": "{count} 次",
|
|
3130
|
+
|
|
3131
|
+
"filter.sumStatus": "状态:{value}",
|
|
3132
|
+
"filter.sumType": "类型:{value}",
|
|
3133
|
+
"filter.sumTag": "标签:#{value}",
|
|
3134
|
+
"filter.sumQuery": "搜索:{value}",
|
|
3135
|
+
"filter.clearAll": "清除全部",
|
|
3136
|
+
"filter.statusLabel": "状态",
|
|
3137
|
+
"filter.all": "全部",
|
|
3138
|
+
"filter.allTitle": "活动与已归档一起看(归档条走灰色)",
|
|
3139
|
+
"filter.active": "活动中",
|
|
3140
|
+
"filter.activeTitle": "正在参与检索与星图的记忆",
|
|
3141
|
+
"filter.archived": "已归档",
|
|
3142
|
+
"filter.archivedTitle": "已收进档案馆:不参与检索与星图,可编辑 / 恢复",
|
|
3143
|
+
"filter.typeLabel": "类型",
|
|
3144
|
+
"filter.tags": "标签",
|
|
3145
|
+
"filter.collapseTags": "收起标签",
|
|
3146
|
+
"filter.moreTagsTitle": "还有一些标签没显示",
|
|
3147
|
+
"filter.moreTags": "更多标签 ({count})",
|
|
3148
|
+
"filter.clear": "清除筛选",
|
|
3149
|
+
"filter.remove": "撤掉这项筛选",
|
|
3150
|
+
|
|
3151
|
+
"files.emptyTitle": "还没有记忆文件。对话中值得记住的事会写到这里,也可以手动写入。",
|
|
3152
|
+
"files.skeletonTitle": "写入一份 MEMORY.md 索引骨架(已有索引不会覆盖)",
|
|
3153
|
+
"files.skeleton": "建立索引骨架",
|
|
3154
|
+
"files.archiveEmpty": "档案馆是空的 —— 在活动记忆那张卡上点「归档」,它就会收到这里。",
|
|
3155
|
+
"files.backToFiles": "回文件列表",
|
|
3156
|
+
"files.activeEmpty": "活动记忆是空的 —— 状态筛到「已归档」还能捞出 {count} 条。",
|
|
3157
|
+
"files.goArchived": "切到已归档",
|
|
3158
|
+
"files.noMatch": "没有符合搜索或筛选条件的文件。",
|
|
3159
|
+
"files.archiveIntro": "档案馆里的记忆不参与检索与星图,原文仍在记忆目录的 archive/ 下 —— 恢复后它就回到活动列表;「编辑」是原样文本编辑,改完存回同一份文件;「彻底删除」会真的从 archive/ 里删掉它(不可恢复,删除前还要再确认一次)。",
|
|
3160
|
+
|
|
3161
|
+
"card.typeTitle": "类型 {type}",
|
|
3162
|
+
"card.collapse": "点击收起",
|
|
3163
|
+
"card.expand": "点击展开全文",
|
|
3164
|
+
"card.archiveWhat": "把「{file}」收进档案馆:它不再参与检索与星图,文件移进 archive/,随时可以恢复。",
|
|
3165
|
+
"card.archiveReason": "归档理由(可留空)",
|
|
3166
|
+
"card.archiveReasonPlaceholder": "例如:结论已被 project_beta 取代",
|
|
3167
|
+
"card.archiveHint": "写下当时的判断,以后翻到它时就知道这条还值不值得捞回来。",
|
|
3168
|
+
"card.confirmArchive": "确认归档",
|
|
3169
|
+
"card.cancel": "取消",
|
|
3170
|
+
"card.confirmDelete": "确认删除",
|
|
3171
|
+
"card.edit": "编辑",
|
|
3172
|
+
"card.lockedTitle": "已锁定:自动归档不会选它,手动归档也会被拒绝。点一下解锁",
|
|
3173
|
+
"card.unlockedTitle": "锁定这条记忆:自动与手动归档都不再动它(重要的人设 / 偏好建议锁上)",
|
|
3174
|
+
"card.locked": "🔒 已锁定",
|
|
3175
|
+
"card.lock": "🔓 锁定",
|
|
3176
|
+
"card.archiveLockedTitle": "这条已锁定,归档会被拒绝(先解锁)",
|
|
3177
|
+
"card.archiveTitle": "收进档案馆:不再参与检索与星图,随时可以恢复",
|
|
3178
|
+
"card.archive": "归档",
|
|
3179
|
+
"card.delete": "删除",
|
|
3180
|
+
"card.archivedAtTitle": "归档时间",
|
|
3181
|
+
"card.archivedAt": "已归档 {at}",
|
|
3182
|
+
"card.unknownTime": "(时间未知)",
|
|
3183
|
+
"card.reason": "归档理由:{reason}",
|
|
3184
|
+
"card.noReason": "(没写)",
|
|
3185
|
+
"card.rawEditNote": "原样文本编辑:下面就是这份文件的全文(含 frontmatter 与归档留痕),保存时原样写回,不会被重新生成。",
|
|
3186
|
+
"card.reading": "读取中…",
|
|
3187
|
+
"card.saving": "保存中…",
|
|
3188
|
+
"card.save": "保存",
|
|
3189
|
+
"card.purgeWhat": "彻底删除「{file}」:它会从 archive/ 里被真的删掉,不能恢复。要保留它就先点「恢复」,把它放回活动记忆。",
|
|
3190
|
+
"card.purgeHint": "这一步之后没有任何界面能把内容找回来 —— 只剩你自己在磁盘上留过备份。",
|
|
3191
|
+
"card.confirmPurge": "确认彻底删除",
|
|
3192
|
+
"card.restoring": "恢复中…",
|
|
3193
|
+
"card.restore": "恢复",
|
|
3194
|
+
"card.purgeTitle": "从 archive/ 里彻底删掉,不可恢复(要留就先恢复)",
|
|
3195
|
+
"card.purge": "彻底删除",
|
|
3196
|
+
|
|
3197
|
+
"star.ask": "⭐ 顺手留颗 Star,作者能高兴一整天",
|
|
3198
|
+
"star.cta": "行,给你一颗 Star",
|
|
3199
|
+
|
|
3200
|
+
"pluginCard.saveFailed": "保存失败(未知原因)",
|
|
3201
|
+
"pluginCard.noSetSettings": "宿主侧还没有 setSettings 接口(插件版本较旧)",
|
|
3202
|
+
"pluginCard.title": "侧栏入口",
|
|
3203
|
+
"pluginCard.on": "已开启",
|
|
3204
|
+
"pluginCard.off": "已关闭",
|
|
3205
|
+
"pluginCard.label": "在侧栏显示「记忆管理」入口",
|
|
3206
|
+
"pluginCard.note": "默认开启;改动立即生效,不需要重启。关闭后「设置 → 记忆管理」入口仍然保留。",
|
|
3207
|
+
|
|
3208
|
+
"error.remote": "remote 调用失败",
|
|
3209
|
+
"error.listFiles": "listFiles RPC 失败: {message}",
|
|
3210
|
+
"error.noRaw": "宿主侧还没有 readRaw / writeRaw 接口(插件版本较旧)",
|
|
3211
|
+
"error.noOpenDir": "宿主侧还没有 openMemoryDir 接口(插件版本较旧)",
|
|
3212
|
+
"error.noArchive": "宿主侧还没有 archive 接口(插件版本较旧)",
|
|
3213
|
+
"error.noLock": "宿主侧还没有 setLocked 接口(插件版本较旧)",
|
|
3214
|
+
"error.noRestore": "宿主侧还没有 restore 接口(插件版本较旧)",
|
|
3215
|
+
"error.noDeleteArchived": "宿主侧还没有 deleteArchived 接口(插件版本较旧)",
|
|
3216
|
+
"error.noReadArchived": "宿主侧还没有 readArchived 接口(插件版本较旧)",
|
|
3217
|
+
"error.noWriteArchived": "宿主侧还没有 writeArchived 接口(插件版本较旧)",
|
|
3218
|
+
"error.noAudit": "宿主侧还没有 audit 接口(插件版本较旧)",
|
|
3219
|
+
"error.noArchiveCandidates": "宿主侧还没有 archiveCandidates 接口(插件版本较旧)",
|
|
3220
|
+
"error.noGetSettings": "宿主侧还没有 getSettings 接口(插件版本较旧)",
|
|
3221
|
+
"error.noReadRaw": "宿主侧还没有 readRaw 接口(插件版本较旧)",
|
|
3222
|
+
"error.noWriteRaw": "宿主侧还没有 writeRaw 接口(插件版本较旧)",
|
|
3223
|
+
"error.readFailed": "读不到 {file}:{message}",
|
|
3224
|
+
"error.descEmpty": "描述不能为空",
|
|
3225
|
+
"error.writeRejected": "写入被拒绝",
|
|
3226
|
+
"error.deleteRejected": "删除被拒绝",
|
|
3227
|
+
"error.archiveRejected": "归档被拒绝",
|
|
3228
|
+
"error.lockRejected": "锁定操作被拒绝",
|
|
3229
|
+
"error.restoreRejected": "恢复被拒绝",
|
|
3230
|
+
"error.saveRejected": "保存被拒绝",
|
|
3231
|
+
"error.skeletonFailed": "建立索引骨架失败",
|
|
3232
|
+
"error.openDirFailed": "打开记忆目录失败",
|
|
3233
|
+
"notice.indexExists": "MEMORY.md 已经存在,没有改动它。",
|
|
3234
|
+
"notice.indexCreated": "已建立 MEMORY.md 索引骨架。",
|
|
3235
|
+
"notice.updated": "已更新 {file}",
|
|
3236
|
+
"notice.saved": "已保存 {file}",
|
|
3237
|
+
"notice.rawNew": "已新建 {file}",
|
|
3238
|
+
"notice.deleted": "已删除 {file}",
|
|
3239
|
+
"notice.archived": "已归档 {file} —— 它现在在「已归档」视图里,随时可以恢复",
|
|
3240
|
+
"notice.batchPartial": "已归档 {done} 条,{failed} 条没成功:{files}",
|
|
3241
|
+
"notice.batchDone": "已归档 {done} 条 —— 它们现在在档案馆里,随时可以恢复",
|
|
3242
|
+
"notice.locked": "已锁定 {file} —— 自动归档不会再选它,手动归档也会被拒绝",
|
|
3243
|
+
"notice.unlocked": "已解锁 {file}",
|
|
3244
|
+
"notice.restored": "已恢复 {file} —— 它回到了活动记忆里",
|
|
3245
|
+
"notice.restoredIndexed": "已恢复 {file} —— 它回到了活动记忆里,并补进了索引",
|
|
3246
|
+
"notice.purged": "已彻底删除 {file} —— 归档区里也没有它了,找不回来",
|
|
3247
|
+
};
|
|
3248
|
+
|
|
3249
|
+
const EN = {
|
|
3250
|
+
"section.label": "Memory management",
|
|
3251
|
+
"page.back": "Back to conversation",
|
|
3252
|
+
"sidebar.label": "Memory management",
|
|
3253
|
+
|
|
3254
|
+
"tab.files": "Files",
|
|
3255
|
+
"tab.star": "Star map",
|
|
3256
|
+
"tab.autodream": "Autodream",
|
|
3257
|
+
"tab.archive": "Auto-archive",
|
|
3258
|
+
"tab.audit": "Audit",
|
|
3259
|
+
|
|
3260
|
+
"type.user": "User",
|
|
3261
|
+
"type.feedback": "Feedback",
|
|
3262
|
+
"type.project": "Project",
|
|
3263
|
+
"type.reference": "Reference",
|
|
3264
|
+
"type.special": "Special",
|
|
3265
|
+
|
|
3266
|
+
"header.dirReadable": "memory directory readable",
|
|
3267
|
+
"header.dirUnreadable": "memory directory unreadable",
|
|
3268
|
+
"header.dirTitle": "{state} · {tab}",
|
|
3269
|
+
"header.counts": "Active {active} · Archived {archived}",
|
|
3270
|
+
"header.countsFiltered": "Showing {shown} / {total} · Active {active} · Archived {archived}",
|
|
3271
|
+
"header.auditSwitch": "Switch to Audit to see which entries",
|
|
3272
|
+
"header.auditClean": "Audit found no problems",
|
|
3273
|
+
"header.auditRun": "Run an audit (read-only)",
|
|
3274
|
+
"header.auditProblems": "Audit: {count} problems",
|
|
3275
|
+
"header.auditPass": "Audit passed",
|
|
3276
|
+
"header.auditNotRun": "Audit · not run",
|
|
3277
|
+
"header.restartTitle": "Changes take effect after restarting DSH",
|
|
3278
|
+
"header.restartPending": "restart to apply",
|
|
3279
|
+
"header.restartPendingCount": "{count} items pending restart",
|
|
3280
|
+
"header.openFolderTitle": "Open the memory directory in the system file manager",
|
|
3281
|
+
"header.openFolder": "Open memory folder",
|
|
3282
|
+
"header.refresh": "Refresh",
|
|
3283
|
+
"action.add": "+ Add memory",
|
|
3284
|
+
|
|
3285
|
+
"form.addTitle": "Add memory",
|
|
3286
|
+
"form.editTitle": "Edit {file}",
|
|
3287
|
+
"form.type": "Type:",
|
|
3288
|
+
"form.fileName": "File name (.md; leave blank to use {type}_note.md automatically)",
|
|
3289
|
+
"form.fileNamePlaceholder": "e.g. project_example.md",
|
|
3290
|
+
"form.desc": "Description (written to frontmatter; retrieval and the list both rely on it)",
|
|
3291
|
+
"form.descPlaceholder": "One sentence saying what this memory is",
|
|
3292
|
+
"form.tags": "Custom tags (optional, comma-separated; written to frontmatter tags, filterable in the list)",
|
|
3293
|
+
"form.tagsPlaceholder": "e.g. deploy, notes, dsh",
|
|
3294
|
+
"form.body": "Body (optional; frontmatter is generated automatically)",
|
|
3295
|
+
"form.bodyPlaceholder": "Details worth remembering long-term",
|
|
3296
|
+
|
|
3297
|
+
"update.found": "New version v{latest} available (current v{current})",
|
|
3298
|
+
"update.notes": "See what's new",
|
|
3299
|
+
"update.dismissTitle": "Skip this time (it will show again next time)",
|
|
3300
|
+
|
|
3301
|
+
"toolbar.search": "Search file name / description / type / tag…",
|
|
3302
|
+
"tools.button": "Tools ▾",
|
|
3303
|
+
"tools.title": "Archive candidates / Reserved-name editor",
|
|
3304
|
+
"tools.titleDebug": "Archive candidates / Reserved-name editor / Injection debug",
|
|
3305
|
+
"tools.candidates": "Archive candidates",
|
|
3306
|
+
"tools.candidatesCount": "{count}",
|
|
3307
|
+
"tools.candidatesEmpty": "not loaded",
|
|
3308
|
+
"tools.raw": "Reserved-name editor",
|
|
3309
|
+
"tools.rawCount": "{count}",
|
|
3310
|
+
"tools.debug": "Injection debug",
|
|
3311
|
+
"tools.debugCount": "{count}",
|
|
3312
|
+
|
|
3313
|
+
"filter.sumStatus": "Status: {value}",
|
|
3314
|
+
"filter.sumType": "Type: {value}",
|
|
3315
|
+
"filter.sumTag": "Tag: #{value}",
|
|
3316
|
+
"filter.sumQuery": "Search: {value}",
|
|
3317
|
+
"filter.clearAll": "Clear all",
|
|
3318
|
+
"filter.statusLabel": "Status",
|
|
3319
|
+
"filter.all": "All",
|
|
3320
|
+
"filter.allTitle": "See active and archived together (archived rows are grey)",
|
|
3321
|
+
"filter.active": "Active",
|
|
3322
|
+
"filter.activeTitle": "Memories currently participating in retrieval and the star map",
|
|
3323
|
+
"filter.archived": "Archived",
|
|
3324
|
+
"filter.archivedTitle": "Moved to the archive: out of retrieval and the star map; can be edited / restored",
|
|
3325
|
+
"filter.typeLabel": "Type",
|
|
3326
|
+
"filter.tags": "Tags",
|
|
3327
|
+
"filter.collapseTags": "Collapse tags",
|
|
3328
|
+
"filter.moreTagsTitle": "Some tags are hidden",
|
|
3329
|
+
"filter.moreTags": "More tags ({count})",
|
|
3330
|
+
"filter.clear": "Clear filters",
|
|
3331
|
+
"filter.remove": "Remove this filter",
|
|
3332
|
+
|
|
3333
|
+
"files.emptyTitle": "No memory files yet. Things worth remembering from a conversation are written here, and you can add them by hand.",
|
|
3334
|
+
"files.skeletonTitle": "Write a MEMORY.md index skeleton (an existing index is never overwritten)",
|
|
3335
|
+
"files.skeleton": "Create index skeleton",
|
|
3336
|
+
"files.archiveEmpty": "The archive is empty — press Archive on an active memory card and it will land here.",
|
|
3337
|
+
"files.backToFiles": "Back to files",
|
|
3338
|
+
"files.activeEmpty": "There are no active memories — switching the status filter to Archived still finds {count}.",
|
|
3339
|
+
"files.goArchived": "Switch to archived",
|
|
3340
|
+
"files.noMatch": "No files match the search or filters.",
|
|
3341
|
+
"files.archiveIntro": "Archived memories stay out of retrieval and the star map; the original text remains in archive/ under the memory directory — restoring returns it to the active list. Edit opens the raw text and saves back to the same file; Delete permanently really removes it from archive/ (irreversible, and asks once more before deleting).",
|
|
3342
|
+
|
|
3343
|
+
"card.typeTitle": "Type {type}",
|
|
3344
|
+
"card.collapse": "Click to collapse",
|
|
3345
|
+
"card.expand": "Click to expand",
|
|
3346
|
+
"card.archiveWhat": "Move “{file}” into the archive: it stops participating in retrieval and the star map, moves under archive/, and can be restored at any time.",
|
|
3347
|
+
"card.archiveReason": "Archive reason (optional)",
|
|
3348
|
+
"card.archiveReasonPlaceholder": "e.g. superseded by project_beta",
|
|
3349
|
+
"card.archiveHint": "Write down the judgment you are making now, so a future reader knows whether this is still worth recovering.",
|
|
3350
|
+
"card.confirmArchive": "Confirm archive",
|
|
3351
|
+
"card.cancel": "Cancel",
|
|
3352
|
+
"card.confirmDelete": "Confirm delete",
|
|
3353
|
+
"card.edit": "Edit",
|
|
3354
|
+
"card.lockedTitle": "Locked: auto-archive never selects it and a manual archive is refused. Click to unlock",
|
|
3355
|
+
"card.unlockedTitle": "Lock this memory: auto and manual archiving both leave it alone (lock important personas / preferences)",
|
|
3356
|
+
"card.locked": "🔒 Locked",
|
|
3357
|
+
"card.lock": "🔓 Lock",
|
|
3358
|
+
"card.archiveLockedTitle": "This memory is locked, so archiving will be refused (unlock first)",
|
|
3359
|
+
"card.archiveTitle": "Move into the archive: out of retrieval and the star map, restorable at any time",
|
|
3360
|
+
"card.archive": "Archive",
|
|
3361
|
+
"card.delete": "Delete",
|
|
3362
|
+
"card.archivedAtTitle": "Archived at",
|
|
3363
|
+
"card.archivedAt": "Archived {at}",
|
|
3364
|
+
"card.unknownTime": "(time unknown)",
|
|
3365
|
+
"card.reason": "Archive reason: {reason}",
|
|
3366
|
+
"card.noReason": "(not written)",
|
|
3367
|
+
"card.rawEditNote": "Raw text editing: what follows is the whole file (including frontmatter and archive traces). Saving writes it back unchanged, without regenerating anything.",
|
|
3368
|
+
"card.reading": "Reading…",
|
|
3369
|
+
"card.saving": "Saving…",
|
|
3370
|
+
"card.save": "Save",
|
|
3371
|
+
"card.purgeWhat": "Permanently delete “{file}”: it is really removed from archive/ and cannot be recovered. To keep it, press Restore first and put it back among the active memories.",
|
|
3372
|
+
"card.purgeHint": "After this step no screen can bring the content back — only a backup you kept on disk can.",
|
|
3373
|
+
"card.confirmPurge": "Confirm permanent delete",
|
|
3374
|
+
"card.restoring": "Restoring…",
|
|
3375
|
+
"card.restore": "Restore",
|
|
3376
|
+
"card.purgeTitle": "Remove permanently from archive/, irreversible (restore first to keep it)",
|
|
3377
|
+
"card.purge": "Delete permanently",
|
|
3378
|
+
|
|
3379
|
+
"star.ask": "⭐ Leave a star — it makes the author's day",
|
|
3380
|
+
"star.cta": "Sure, here's a star",
|
|
3381
|
+
|
|
3382
|
+
"pluginCard.saveFailed": "Save failed (unknown reason)",
|
|
3383
|
+
"pluginCard.noSetSettings": "The host has no setSettings API yet (older plugin version)",
|
|
3384
|
+
"pluginCard.title": "Sidebar entry",
|
|
3385
|
+
"pluginCard.on": "On",
|
|
3386
|
+
"pluginCard.off": "Off",
|
|
3387
|
+
"pluginCard.label": "Show the “Memory management” entry in the sidebar",
|
|
3388
|
+
"pluginCard.note": "On by default; changes take effect immediately, no restart needed. Turning it off keeps the Settings → Memory management entry.",
|
|
3389
|
+
|
|
3390
|
+
"error.remote": "remote call failed",
|
|
3391
|
+
"error.listFiles": "listFiles RPC failed: {message}",
|
|
3392
|
+
"error.noRaw": "The host has no readRaw / writeRaw API yet (older plugin version)",
|
|
3393
|
+
"error.noOpenDir": "The host has no openMemoryDir API yet (older plugin version)",
|
|
3394
|
+
"error.noArchive": "The host has no archive API yet (older plugin version)",
|
|
3395
|
+
"error.noLock": "The host has no setLocked API yet (older plugin version)",
|
|
3396
|
+
"error.noRestore": "The host has no restore API yet (older plugin version)",
|
|
3397
|
+
"error.noDeleteArchived": "The host has no deleteArchived API yet (older plugin version)",
|
|
3398
|
+
"error.noReadArchived": "The host has no readArchived API yet (older plugin version)",
|
|
3399
|
+
"error.noWriteArchived": "The host has no writeArchived API yet (older plugin version)",
|
|
3400
|
+
"error.noAudit": "The host has no audit API yet (older plugin version)",
|
|
3401
|
+
"error.noArchiveCandidates": "The host has no archiveCandidates API yet (older plugin version)",
|
|
3402
|
+
"error.noGetSettings": "The host has no getSettings API yet (older plugin version)",
|
|
3403
|
+
"error.noReadRaw": "The host has no readRaw API yet (older plugin version)",
|
|
3404
|
+
"error.noWriteRaw": "The host has no writeRaw API yet (older plugin version)",
|
|
3405
|
+
"error.readFailed": "Cannot read {file}: {message}",
|
|
3406
|
+
"error.descEmpty": "Description cannot be empty",
|
|
3407
|
+
"error.writeRejected": "Write rejected",
|
|
3408
|
+
"error.deleteRejected": "Delete rejected",
|
|
3409
|
+
"error.archiveRejected": "Archive rejected",
|
|
3410
|
+
"error.lockRejected": "Lock operation rejected",
|
|
3411
|
+
"error.restoreRejected": "Restore rejected",
|
|
3412
|
+
"error.saveRejected": "Save rejected",
|
|
3413
|
+
"error.skeletonFailed": "Failed to create the index skeleton",
|
|
3414
|
+
"error.openDirFailed": "Failed to open the memory directory",
|
|
3415
|
+
"notice.indexExists": "MEMORY.md already exists; it was left unchanged.",
|
|
3416
|
+
"notice.indexCreated": "MEMORY.md index skeleton created.",
|
|
3417
|
+
"notice.updated": "Updated {file}",
|
|
3418
|
+
"notice.saved": "Saved {file}",
|
|
3419
|
+
"notice.rawNew": "Created {file}",
|
|
3420
|
+
"notice.deleted": "Deleted {file}",
|
|
3421
|
+
"notice.archived": "Archived {file} — it is now under the Archived view and can be restored at any time",
|
|
3422
|
+
"notice.batchPartial": "Archived {done}, {failed} failed: {files}",
|
|
3423
|
+
"notice.batchDone": "Archived {done} — they are now in the archive and can be restored at any time",
|
|
3424
|
+
"notice.locked": "Locked {file} — auto-archive will not select it and a manual archive will be refused",
|
|
3425
|
+
"notice.unlocked": "Unlocked {file}",
|
|
3426
|
+
"notice.restored": "Restored {file} — it is back among the active memories",
|
|
3427
|
+
"notice.restoredIndexed": "Restored {file} — it is back among the active memories and was added to the index",
|
|
3428
|
+
"notice.purged": "Permanently deleted {file} — it is gone from the archive as well and cannot be recovered",
|
|
3429
|
+
};
|
|
3430
|
+
|
|
3431
|
+
/**
|
|
3432
|
+
* Local placeholder fill, used only when the host has no locale service.
|
|
3433
|
+
* Mirrors the locale runtime: `{name}` is replaced, unknown names stay.
|
|
3434
|
+
*/
|
|
3435
|
+
function i18nFill(template, params) {
|
|
3436
|
+
if (!params) return template;
|
|
3437
|
+
return String(template).replace(/\{(\w+)\}/g, function (match, name) {
|
|
3438
|
+
return Object.prototype.hasOwnProperty.call(params, name) ? String(params[name]) : match;
|
|
3439
|
+
});
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
/** The zh dictionary is the fallback: a host without locale renders today's UI. */
|
|
3443
|
+
function i18nZh(key, params) {
|
|
3444
|
+
return i18nFill(Object.prototype.hasOwnProperty.call(ZH, key) ? ZH[key] : key, params);
|
|
3445
|
+
}
|
|
3446
|
+
|
|
3447
|
+
// Bound in `apply` when (and only when) the host provides the locale service.
|
|
3448
|
+
let t = i18nZh;
|
|
3449
|
+
|
|
3450
|
+
/** Known type ids get translated; unknown frontmatter values stay visible. */
|
|
3451
|
+
function typeLabel(id) {
|
|
3452
|
+
const key = "type." + String(id);
|
|
3453
|
+
return Object.prototype.hasOwnProperty.call(ZH, key) ? t(key) : String(id);
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3456
|
+
/** Same for the tab names used in the directory tooltip. */
|
|
3457
|
+
function tabLabel(id) {
|
|
3458
|
+
const key = "tab." + String(id);
|
|
3459
|
+
return Object.prototype.hasOwnProperty.call(ZH, key) ? t(key) : "";
|
|
3460
|
+
}
|
|
3461
|
+
|
|
3462
|
+
/**
|
|
3463
|
+
* Register the dictionaries and bind `t`, optionally.
|
|
3464
|
+
*
|
|
3465
|
+
* `locale` is NOT a hard dependency (it is absent in older hosts, the
|
|
3466
|
+
* render harness and the unit tests): without it everything above keeps
|
|
3467
|
+
* rendering from `ZH`. Registration goes through `ctx.effect`, so the
|
|
3468
|
+
* dictionary is withdrawn when this plugin is disposed.
|
|
3469
|
+
*/
|
|
3470
|
+
function setupI18n(ctx) {
|
|
3471
|
+
let locale = null;
|
|
3472
|
+
try {
|
|
3473
|
+
if (ctx && typeof ctx.get === "function") locale = ctx.get("locale");
|
|
3474
|
+
if (!locale && ctx && ctx.locale) locale = ctx.locale;
|
|
3475
|
+
} catch (e) {
|
|
3476
|
+
locale = null;
|
|
3477
|
+
}
|
|
3478
|
+
if (!locale || typeof locale.bind !== "function") return false;
|
|
3479
|
+
const install = function () {
|
|
3480
|
+
let dispose = null;
|
|
3481
|
+
if (typeof locale.register === "function") {
|
|
3482
|
+
dispose = locale.register(NS, { zh: ZH, en: EN });
|
|
3483
|
+
} else {
|
|
3484
|
+
return null;
|
|
3485
|
+
}
|
|
3486
|
+
t = locale.bind(NS);
|
|
3487
|
+
return dispose;
|
|
3488
|
+
};
|
|
3489
|
+
if (typeof ctx.effect === "function") ctx.effect(install, "sage-mem: i18n " + NS);
|
|
3490
|
+
else install();
|
|
3491
|
+
return true;
|
|
3492
|
+
}
|
|
3057
3493
|
/**
|
|
3058
3494
|
* 「自动做梦」面板 —— 让记忆自己整理一遍(引擎在 宿主侧 lib/autodream.js)。
|
|
3059
3495
|
*
|
|
@@ -3954,24 +4390,24 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
3954
4390
|
}));
|
|
3955
4391
|
} else if (totalCount === 0) {
|
|
3956
4392
|
filesView = h("div", { className: "smem-empty", key: "empty" }, [
|
|
3957
|
-
h("div", { key: "t" }, "
|
|
3958
|
-
h("button", { type: "button", className: "smem-btn smem-btn-primary", key: "add", onClick: openAdd }, "
|
|
3959
|
-
h("button", { type: "button", className: "smem-btn", key: "idx", title: "
|
|
4393
|
+
h("div", { key: "t" }, t("files.emptyTitle")),
|
|
4394
|
+
h("button", { type: "button", className: "smem-btn smem-btn-primary", key: "add", onClick: openAdd }, t("action.add")),
|
|
4395
|
+
h("button", { type: "button", className: "smem-btn", key: "idx", title: t("files.skeletonTitle"), onClick: createIndexSkeleton }, t("files.skeleton")),
|
|
3960
4396
|
]);
|
|
3961
4397
|
} else if (statusFilter === "archived" && archiveItems.length === 0) {
|
|
3962
4398
|
filesView = h("div", { className: "smem-empty", key: "empty" }, [
|
|
3963
|
-
h("div", { key: "t" }, "
|
|
3964
|
-
h("button", { type: "button", className: "smem-btn", key: "back", onClick: function () { setStatusFilter("active"); } }, "
|
|
4399
|
+
h("div", { key: "t" }, t("files.archiveEmpty")),
|
|
4400
|
+
h("button", { type: "button", className: "smem-btn", key: "back", onClick: function () { setStatusFilter("active"); } }, t("files.backToFiles")),
|
|
3965
4401
|
]);
|
|
3966
4402
|
} else if (statusFilter === "active" && items.length === 0) {
|
|
3967
4403
|
filesView = h("div", { className: "smem-empty", key: "empty" }, [
|
|
3968
|
-
h("div", { key: "t" }, "
|
|
3969
|
-
h("button", { type: "button", className: "smem-btn", key: "go", onClick: function () { setStatusFilter("archived"); } }, "
|
|
4404
|
+
h("div", { key: "t" }, t("files.activeEmpty", { count: archiveItems.length })),
|
|
4405
|
+
h("button", { type: "button", className: "smem-btn", key: "go", onClick: function () { setStatusFilter("archived"); } }, t("files.goArchived")),
|
|
3970
4406
|
]);
|
|
3971
4407
|
} else if (filtered.length === 0) {
|
|
3972
4408
|
filesView = h("div", { className: "smem-empty", key: "empty" }, [
|
|
3973
|
-
h("div", { key: "t" }, "
|
|
3974
|
-
h("button", { type: "button", className: "smem-btn", key: "clear", onClick: function () { setStatusFilter("all"); setTypeFilter([]); setTagFilter([]); setQuery(""); } }, "
|
|
4409
|
+
h("div", { key: "t" }, t("files.noMatch")),
|
|
4410
|
+
h("button", { type: "button", className: "smem-btn", key: "clear", onClick: function () { setStatusFilter("all"); setTypeFilter([]); setTagFilter([]); setQuery(""); } }, t("filter.clear")),
|
|
3975
4411
|
]);
|
|
3976
4412
|
} else {
|
|
3977
4413
|
const list = h("div", { className: "smem-list", key: "list" }, filtered.map(function (it) {
|
|
@@ -4023,9 +4459,7 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4023
4459
|
}));
|
|
4024
4460
|
filesView = statusFilter === "archived"
|
|
4025
4461
|
? [
|
|
4026
|
-
h("div", { className: "smem-ar-banner", key: "banner" },
|
|
4027
|
-
"档案馆里的记忆不参与检索与星图,原文仍在记忆目录的 archive/ 下 —— 恢复后它就回到活动列表;" +
|
|
4028
|
-
"「编辑」是原样文本编辑,改完存回同一份文件;「彻底删除」会真的从 archive/ 里删掉它(不可恢复,删除前还要再确认一次)。"),
|
|
4462
|
+
h("div", { className: "smem-ar-banner", key: "banner" }, t("files.archiveIntro")),
|
|
4029
4463
|
list,
|
|
4030
4464
|
]
|
|
4031
4465
|
: list;
|
|
@@ -4044,56 +4478,56 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4044
4478
|
hasFilter
|
|
4045
4479
|
? h("div", { className: "smem-filter-summary", key: "sum" }, [
|
|
4046
4480
|
statusFilter !== "all"
|
|
4047
|
-
? sumItem("
|
|
4481
|
+
? sumItem(t("filter.sumStatus", { value: statusFilter === "active" ? t("filter.active") : t("filter.archived") }), function () { setStatusFilter("all"); })
|
|
4048
4482
|
: null,
|
|
4049
|
-
typeFilter.map(function (
|
|
4050
|
-
return sumItem("
|
|
4483
|
+
typeFilter.map(function (typeId) {
|
|
4484
|
+
return sumItem(t("filter.sumType", { value: typeLabel(typeId) }), function () { toggleIn(setTypeFilter, typeId); });
|
|
4051
4485
|
}),
|
|
4052
4486
|
tagFilter.map(function (g) {
|
|
4053
|
-
return sumItem("
|
|
4487
|
+
return sumItem(t("filter.sumTag", { value: g }), function () { toggleIn(setTagFilter, g); });
|
|
4054
4488
|
}),
|
|
4055
|
-
query.trim() ? sumItem("
|
|
4489
|
+
query.trim() ? sumItem(t("filter.sumQuery", { value: query.trim() }), function () { setQuery(""); }) : null,
|
|
4056
4490
|
h("button", {
|
|
4057
4491
|
type: "button", className: "smem-sum-clear", key: "all",
|
|
4058
4492
|
onClick: function () { setStatusFilter("all"); setTypeFilter([]); setTagFilter([]); setQuery(""); },
|
|
4059
|
-
}, "
|
|
4493
|
+
}, t("filter.clearAll")),
|
|
4060
4494
|
])
|
|
4061
4495
|
: null,
|
|
4062
|
-
h("span", { className: "smem-filters-label", key: "lst" }, "
|
|
4496
|
+
h("span", { className: "smem-filters-label", key: "lst" }, t("filter.statusLabel")),
|
|
4063
4497
|
h("button", {
|
|
4064
4498
|
type: "button", key: "st-all", className: "smem-chip",
|
|
4065
4499
|
"data-on": statusFilter === "all" ? "1" : "0",
|
|
4066
|
-
title: "
|
|
4500
|
+
title: t("filter.allTitle"),
|
|
4067
4501
|
onClick: function () { setStatusFilter("all"); },
|
|
4068
|
-
}, ["
|
|
4502
|
+
}, [t("filter.all"), h("span", { className: "smem-chip-n", key: "n" }, String(totalCount))]),
|
|
4069
4503
|
h("button", {
|
|
4070
4504
|
type: "button", key: "st-active", className: "smem-chip",
|
|
4071
4505
|
"data-on": statusFilter === "active" ? "1" : "0",
|
|
4072
|
-
title: "
|
|
4506
|
+
title: t("filter.activeTitle"),
|
|
4073
4507
|
onClick: function () { setStatusFilter("active"); },
|
|
4074
|
-
}, ["
|
|
4508
|
+
}, [t("filter.active"), h("span", { className: "smem-chip-n", key: "n" }, String(items.length))]),
|
|
4075
4509
|
h("button", {
|
|
4076
4510
|
type: "button", key: "st-arch", className: "smem-chip",
|
|
4077
4511
|
"data-on": statusFilter === "archived" ? "1" : "0",
|
|
4078
|
-
title: "
|
|
4512
|
+
title: t("filter.archivedTitle"),
|
|
4079
4513
|
onClick: function () { setStatusFilter("archived"); },
|
|
4080
|
-
}, ["
|
|
4514
|
+
}, [t("filter.archived"), h("span", { className: "smem-chip-n", key: "n" }, String(archiveItems.length))]),
|
|
4081
4515
|
h("span", { className: "smem-sep", key: "sep0" }),
|
|
4082
|
-
h("span", { className: "smem-filters-label", key: "lt" }, "
|
|
4516
|
+
h("span", { className: "smem-filters-label", key: "lt" }, t("filter.typeLabel")),
|
|
4083
4517
|
h("button", {
|
|
4084
4518
|
type: "button", key: "all", className: "smem-chip",
|
|
4085
4519
|
"data-on": typeFilter.length ? "0" : "1",
|
|
4086
4520
|
onClick: function () { setTypeFilter([]); },
|
|
4087
|
-
}, ["
|
|
4088
|
-
typeList.map(function (
|
|
4521
|
+
}, [t("filter.all"), h("span", { className: "smem-chip-n", key: "n" }, String(statusItems.length))]),
|
|
4522
|
+
typeList.map(function (typeId) {
|
|
4089
4523
|
return h("button", {
|
|
4090
|
-
type: "button", key:
|
|
4091
|
-
"data-on": typeFilter.indexOf(
|
|
4092
|
-
onClick: function () { toggleIn(setTypeFilter,
|
|
4093
|
-
}, [
|
|
4524
|
+
type: "button", key: typeId, className: "smem-chip",
|
|
4525
|
+
"data-on": typeFilter.indexOf(typeId) >= 0 ? "1" : "0",
|
|
4526
|
+
onClick: function () { toggleIn(setTypeFilter, typeId); },
|
|
4527
|
+
}, [typeLabel(typeId), h("span", { className: "smem-chip-n", key: "n" }, String(typeCounts[typeId]))]);
|
|
4094
4528
|
}),
|
|
4095
4529
|
tagList.length ? h("span", { className: "smem-sep", key: "sep" }) : null,
|
|
4096
|
-
tagList.length ? h("span", { className: "smem-filters-label", key: "lgt" }, "
|
|
4530
|
+
tagList.length ? h("span", { className: "smem-filters-label", key: "lgt" }, t("filter.tags")) : null,
|
|
4097
4531
|
tagShown.map(function (g) {
|
|
4098
4532
|
return h("button", {
|
|
4099
4533
|
type: "button", key: "tag-" + g, className: "smem-chip",
|
|
@@ -4104,15 +4538,15 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4104
4538
|
tagList.length > TAG_VISIBLE_MAX
|
|
4105
4539
|
? h("button", {
|
|
4106
4540
|
type: "button", key: "moretag", className: "smem-btn",
|
|
4107
|
-
title: tagOpen ? "
|
|
4541
|
+
title: tagOpen ? t("filter.collapseTags") : t("filter.moreTagsTitle"),
|
|
4108
4542
|
onClick: function () { setTagOpen(!tagOpen); },
|
|
4109
|
-
}, tagOpen ? "
|
|
4543
|
+
}, tagOpen ? t("filter.collapseTags") : t("filter.moreTags", { count: String(tagHiddenCount) }) )
|
|
4110
4544
|
: null,
|
|
4111
4545
|
hasFilter
|
|
4112
4546
|
? h("button", {
|
|
4113
4547
|
type: "button", key: "clear", className: "smem-btn",
|
|
4114
4548
|
onClick: function () { setStatusFilter("all"); setTypeFilter([]); setTagFilter([]); setQuery(""); },
|
|
4115
|
-
}, "
|
|
4549
|
+
}, t("filter.clear"))
|
|
4116
4550
|
: null,
|
|
4117
4551
|
|
|
4118
4552
|
]);
|
|
@@ -4260,8 +4694,8 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4260
4694
|
remote.listFiles()
|
|
4261
4695
|
.then(function (res) {
|
|
4262
4696
|
if (res && typeof res === "object" && res.ok === false) {
|
|
4263
|
-
const msg = res.error && res.error.message ? res.error.message : "remote
|
|
4264
|
-
throw new Error("listFiles
|
|
4697
|
+
const msg = res.error && res.error.message ? res.error.message : t("error.remote");
|
|
4698
|
+
throw new Error(t("error.listFiles", { message: msg }));
|
|
4265
4699
|
}
|
|
4266
4700
|
const list = unwrap(res);
|
|
4267
4701
|
setItems(Array.isArray(list) ? list : []);
|
|
@@ -4374,20 +4808,20 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4374
4808
|
setError(null);
|
|
4375
4809
|
setNotice("");
|
|
4376
4810
|
if (!remote || typeof remote.readRaw !== "function" || typeof remote.writeRaw !== "function") {
|
|
4377
|
-
setError("
|
|
4811
|
+
setError(t("error.noRaw"));
|
|
4378
4812
|
return;
|
|
4379
4813
|
}
|
|
4380
4814
|
setBusy(true);
|
|
4381
4815
|
// 先读再写:**已有索引绝不覆盖**。readRaw 对不存在的文件会 reject,正好当探针。
|
|
4382
4816
|
remote.readRaw("MEMORY.md").then(function () {
|
|
4383
4817
|
setBusy(false);
|
|
4384
|
-
setNotice("
|
|
4818
|
+
setNotice(t("notice.indexExists"));
|
|
4385
4819
|
}, function () {
|
|
4386
4820
|
return remote.writeRaw("MEMORY.md", INDEX_SKELETON).then(function (res) {
|
|
4387
4821
|
const out = unwrap(res);
|
|
4388
4822
|
setBusy(false);
|
|
4389
|
-
if (out && out.ok === false) { setError(out.error || "
|
|
4390
|
-
setNotice("
|
|
4823
|
+
if (out && out.ok === false) { setError(out.error || t("error.skeletonFailed")); return; }
|
|
4824
|
+
setNotice(t("notice.indexCreated"));
|
|
4391
4825
|
load();
|
|
4392
4826
|
});
|
|
4393
4827
|
}).catch(function (e) {
|
|
@@ -4399,13 +4833,13 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4399
4833
|
function openMemoryFolder() {
|
|
4400
4834
|
setError(null);
|
|
4401
4835
|
if (!remote || typeof remote.openMemoryDir !== "function") {
|
|
4402
|
-
setError("
|
|
4836
|
+
setError(t("error.noOpenDir"));
|
|
4403
4837
|
return;
|
|
4404
4838
|
}
|
|
4405
4839
|
remote.openMemoryDir()
|
|
4406
4840
|
.then(function (res) {
|
|
4407
4841
|
const result = unwrap(res);
|
|
4408
|
-
if (result && result.ok === false) setError(result.error || "
|
|
4842
|
+
if (result && result.ok === false) setError(result.error || t("error.openDirFailed"));
|
|
4409
4843
|
})
|
|
4410
4844
|
.catch(function (e) { setError(String((e && e.message) || e)); });
|
|
4411
4845
|
}
|
|
@@ -4452,7 +4886,7 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4452
4886
|
}
|
|
4453
4887
|
|
|
4454
4888
|
function submit() {
|
|
4455
|
-
if (!formDesc.trim()) { setError("
|
|
4889
|
+
if (!formDesc.trim()) { setError(t("error.descEmpty")); return; }
|
|
4456
4890
|
setBusy(true); setError(null); setNotice("");
|
|
4457
4891
|
const type = formType;
|
|
4458
4892
|
const name = (formName.trim() || (type + "_note.md"));
|
|
@@ -4477,9 +4911,9 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4477
4911
|
remote.writeFile(safeName, content)
|
|
4478
4912
|
.then(function (r) {
|
|
4479
4913
|
// host 侧对保留名 / 非字符串内容 / 超限一律返回 { ok: false, error },不再抛异常。
|
|
4480
|
-
if (r && r.ok === false) { setError(r.error || "
|
|
4914
|
+
if (r && r.ok === false) { setError(r.error || t("error.writeRejected")); return; }
|
|
4481
4915
|
closeForm();
|
|
4482
|
-
setNotice(editingFile ? "
|
|
4916
|
+
setNotice(t(editingFile ? "notice.updated" : "notice.saved", { file: safeName }));
|
|
4483
4917
|
load();
|
|
4484
4918
|
})
|
|
4485
4919
|
.catch(function (e) { setError(String((e && e.message) || e)); })
|
|
@@ -4490,9 +4924,9 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4490
4924
|
setBusy(true); setError(null); setNotice("");
|
|
4491
4925
|
remote.deleteFile(file)
|
|
4492
4926
|
.then(function (r) {
|
|
4493
|
-
if (r && r.ok === false) { setError(r.error || "
|
|
4927
|
+
if (r && r.ok === false) { setError(r.error || t("error.deleteRejected")); return; }
|
|
4494
4928
|
setDeleting(null);
|
|
4495
|
-
setNotice("
|
|
4929
|
+
setNotice(t("notice.deleted", { file: file }));
|
|
4496
4930
|
load();
|
|
4497
4931
|
})
|
|
4498
4932
|
.catch(function (e) { setError(String((e && e.message) || e)); })
|
|
@@ -4507,16 +4941,16 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4507
4941
|
*/
|
|
4508
4942
|
function confirmArchive(file) {
|
|
4509
4943
|
if (typeof remote.archive !== "function") {
|
|
4510
|
-
setError("
|
|
4944
|
+
setError(t("error.noArchive"));
|
|
4511
4945
|
return;
|
|
4512
4946
|
}
|
|
4513
4947
|
setBusy(true); setError(null); setNotice("");
|
|
4514
4948
|
remote.archive(file, archiveReason.trim())
|
|
4515
4949
|
.then(function (r) {
|
|
4516
|
-
if (r && r.ok === false) { setError(r.error || "
|
|
4950
|
+
if (r && r.ok === false) { setError(r.error || t("error.archiveRejected")); return; }
|
|
4517
4951
|
setArchiving(null);
|
|
4518
4952
|
setArchiveReason("");
|
|
4519
|
-
setNotice("
|
|
4953
|
+
setNotice(t("notice.archived", { file: file }));
|
|
4520
4954
|
load();
|
|
4521
4955
|
})
|
|
4522
4956
|
.catch(function (e) { setError(String((e && e.message) || e)); })
|
|
@@ -4531,17 +4965,17 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4531
4965
|
*/
|
|
4532
4966
|
function toggleLock(file, locked) {
|
|
4533
4967
|
if (typeof remote.setLocked !== "function") {
|
|
4534
|
-
setError("
|
|
4968
|
+
setError(t("error.noLock"));
|
|
4535
4969
|
return;
|
|
4536
4970
|
}
|
|
4537
4971
|
setBusy(true); setError(null); setNotice("");
|
|
4538
4972
|
remote.setLocked(file, locked)
|
|
4539
4973
|
.then(function (r) {
|
|
4540
4974
|
const d = unwrap(r);
|
|
4541
|
-
if (d && d.ok === false) { setError(d.error || "
|
|
4975
|
+
if (d && d.ok === false) { setError(d.error || t("error.lockRejected")); return; }
|
|
4542
4976
|
setNotice(locked
|
|
4543
|
-
? ("
|
|
4544
|
-
: ("
|
|
4977
|
+
? t("notice.locked", { file: file })
|
|
4978
|
+
: t("notice.unlocked", { file: file }));
|
|
4545
4979
|
load();
|
|
4546
4980
|
})
|
|
4547
4981
|
.catch(function (e) { setError(String((e && e.message) || e)); })
|
|
@@ -4550,7 +4984,7 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4550
4984
|
|
|
4551
4985
|
/** 从档案馆捞回来:文件移回记忆目录,归档留痕被抹掉。 */
|
|
4552
4986
|
function restoreFile(file) { if (typeof remote.restore !== "function") {
|
|
4553
|
-
setError("
|
|
4987
|
+
setError(t("error.noRestore"));
|
|
4554
4988
|
return;
|
|
4555
4989
|
}
|
|
4556
4990
|
setBusy(true); setError(null); setNotice("");
|
|
@@ -4558,9 +4992,9 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4558
4992
|
setArchDelete(null);
|
|
4559
4993
|
remote.restore(file)
|
|
4560
4994
|
.then(function (r) {
|
|
4561
|
-
if (r && r.ok === false) { setError(r.error || "
|
|
4995
|
+
if (r && r.ok === false) { setError(r.error || t("error.restoreRejected")); return; }
|
|
4562
4996
|
// 恢复会顺带把这条补进索引:说清楚,否则用户不知道「漏索引」是怎么好的
|
|
4563
|
-
setNotice(
|
|
4997
|
+
setNotice(t(r && r.indexed ? "notice.restoredIndexed" : "notice.restored", { file: file }));
|
|
4564
4998
|
load();
|
|
4565
4999
|
})
|
|
4566
5000
|
.catch(function (e) { setError(String((e && e.message) || e)); })
|
|
@@ -4575,7 +5009,7 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4575
5009
|
*/
|
|
4576
5010
|
function deleteArchivedFile(file) {
|
|
4577
5011
|
if (typeof remote.deleteArchived !== "function") {
|
|
4578
|
-
setError("
|
|
5012
|
+
setError(t("error.noDeleteArchived"));
|
|
4579
5013
|
return;
|
|
4580
5014
|
}
|
|
4581
5015
|
const item = archiveItems.filter(function (x) { return x.file === file; })[0];
|
|
@@ -4583,9 +5017,9 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4583
5017
|
setBusy(true); setError(null); setNotice("");
|
|
4584
5018
|
remote.deleteArchived(file, stamp)
|
|
4585
5019
|
.then(function (r) {
|
|
4586
|
-
if (r && r.ok === false) { setError(r.error || "
|
|
5020
|
+
if (r && r.ok === false) { setError(r.error || t("error.deleteRejected")); return; }
|
|
4587
5021
|
setArchDelete(null);
|
|
4588
|
-
setNotice("
|
|
5022
|
+
setNotice(t("notice.purged", { file: file }));
|
|
4589
5023
|
load();
|
|
4590
5024
|
})
|
|
4591
5025
|
.catch(function (e) { setError(String((e && e.message) || e)); })
|
|
@@ -4602,7 +5036,7 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4602
5036
|
*/
|
|
4603
5037
|
function openArchEdit(file) {
|
|
4604
5038
|
if (typeof remote.readArchived !== "function") {
|
|
4605
|
-
setError("
|
|
5039
|
+
setError(t("error.noReadArchived"));
|
|
4606
5040
|
return;
|
|
4607
5041
|
}
|
|
4608
5042
|
setError(null); setNotice("");
|
|
@@ -4625,16 +5059,16 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4625
5059
|
|
|
4626
5060
|
function saveArchEdit(file) {
|
|
4627
5061
|
if (typeof remote.writeArchived !== "function") {
|
|
4628
|
-
setError("
|
|
5062
|
+
setError(t("error.noWriteArchived"));
|
|
4629
5063
|
return;
|
|
4630
5064
|
}
|
|
4631
5065
|
setArchSaving(true); setError(null); setNotice("");
|
|
4632
5066
|
// 原样交回:不 trim、不补 frontmatter、不做任何「智能」处理。
|
|
4633
5067
|
remote.writeArchived(file, archText)
|
|
4634
5068
|
.then(function (r) {
|
|
4635
|
-
if (r && r.ok === false) { setError(r.error || "
|
|
5069
|
+
if (r && r.ok === false) { setError(r.error || t("error.saveRejected")); return; }
|
|
4636
5070
|
setArchEdit(null);
|
|
4637
|
-
setNotice("
|
|
5071
|
+
setNotice(t("notice.saved", { file: file }));
|
|
4638
5072
|
load();
|
|
4639
5073
|
})
|
|
4640
5074
|
.catch(function (e) { setError(String((e && e.message) || e)); })
|
|
@@ -4646,7 +5080,7 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4646
5080
|
// ── 工具面板的三个数据通道 ──────────────────────────────────────
|
|
4647
5081
|
|
|
4648
5082
|
function runAudit() {
|
|
4649
|
-
if (typeof remote.audit !== "function") { setError("
|
|
5083
|
+
if (typeof remote.audit !== "function") { setError(t("error.noAudit")); return; }
|
|
4650
5084
|
setAuditBusy(true); setError(null);
|
|
4651
5085
|
remote.audit()
|
|
4652
5086
|
.then(function (r) { setAudit(unwrap(r) || null); })
|
|
@@ -4655,7 +5089,7 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4655
5089
|
}
|
|
4656
5090
|
|
|
4657
5091
|
function loadCandidates() {
|
|
4658
|
-
if (typeof remote.archiveCandidates !== "function") { setError("
|
|
5092
|
+
if (typeof remote.archiveCandidates !== "function") { setError(t("error.noArchiveCandidates")); return; }
|
|
4659
5093
|
setCandBusy(true); setError(null);
|
|
4660
5094
|
remote.archiveCandidates()
|
|
4661
5095
|
.then(function (r) {
|
|
@@ -4672,7 +5106,7 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4672
5106
|
}
|
|
4673
5107
|
|
|
4674
5108
|
function loadSettings() {
|
|
4675
|
-
if (typeof remote.getSettings !== "function") { setError("
|
|
5109
|
+
if (typeof remote.getSettings !== "function") { setError(t("error.noGetSettings")); return; }
|
|
4676
5110
|
remote.getSettings()
|
|
4677
5111
|
.then(function (r) { const d = unwrap(r) || {}; setSettings(d); })
|
|
4678
5112
|
.catch(function (e) { setError(String((e && e.message) || e)); });
|
|
@@ -4740,7 +5174,7 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4740
5174
|
*/
|
|
4741
5175
|
function archiveCandidates(list) {
|
|
4742
5176
|
if (!list.length) return;
|
|
4743
|
-
if (typeof remote.archive !== "function") { setError("
|
|
5177
|
+
if (typeof remote.archive !== "function") { setError(t("error.noArchive")); return; }
|
|
4744
5178
|
setBusy(true); setError(null); setNotice("");
|
|
4745
5179
|
const done = [];
|
|
4746
5180
|
const failed = [];
|
|
@@ -4750,8 +5184,8 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4750
5184
|
setCandConfirm(null);
|
|
4751
5185
|
setPicked({});
|
|
4752
5186
|
setNotice(failed.length
|
|
4753
|
-
? ("
|
|
4754
|
-
: ("
|
|
5187
|
+
? t("notice.batchPartial", { done: done.length, failed: failed.length, files: failed.join("、") })
|
|
5188
|
+
: t("notice.batchDone", { done: done.length }));
|
|
4755
5189
|
load();
|
|
4756
5190
|
loadCandidates();
|
|
4757
5191
|
return;
|
|
@@ -4768,7 +5202,7 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4768
5202
|
}
|
|
4769
5203
|
|
|
4770
5204
|
function openRaw(name) {
|
|
4771
|
-
if (typeof remote.readRaw !== "function") { setError("
|
|
5205
|
+
if (typeof remote.readRaw !== "function") { setError(t("error.noReadRaw")); return; }
|
|
4772
5206
|
setError(null); setNotice("");
|
|
4773
5207
|
setRawName(name);
|
|
4774
5208
|
setRawText("");
|
|
@@ -4791,20 +5225,20 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4791
5225
|
setRawLoading(false);
|
|
4792
5226
|
return;
|
|
4793
5227
|
}
|
|
4794
|
-
setError("
|
|
5228
|
+
setError(t("error.readFailed", { file: name, message: msg }));
|
|
4795
5229
|
setRawLoading(false);
|
|
4796
5230
|
setRawName(null);
|
|
4797
5231
|
});
|
|
4798
5232
|
}
|
|
4799
5233
|
|
|
4800
5234
|
function saveRaw(name) {
|
|
4801
|
-
if (typeof remote.writeRaw !== "function") { setError("
|
|
5235
|
+
if (typeof remote.writeRaw !== "function") { setError(t("error.noWriteRaw")); return; }
|
|
4802
5236
|
setRawBusy(true); setError(null); setNotice("");
|
|
4803
5237
|
// 原样交回:不 trim、不补 frontmatter、不做任何「智能」处理。
|
|
4804
5238
|
remote.writeRaw(name, rawText)
|
|
4805
5239
|
.then(function (r) {
|
|
4806
|
-
if (r && r.ok === false) { setError(r.error || "
|
|
4807
|
-
setNotice((rawIsNew ? "
|
|
5240
|
+
if (r && r.ok === false) { setError(r.error || t("error.saveRejected")); return; }
|
|
5241
|
+
setNotice(t(rawIsNew ? "notice.rawNew" : "notice.saved", { file: name }));
|
|
4808
5242
|
setRawIsNew(false);
|
|
4809
5243
|
})
|
|
4810
5244
|
.catch(function (e) { setError(String((e && e.message) || e)); })
|
|
@@ -4873,12 +5307,13 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4873
5307
|
function sumItem(label, onRemove) {
|
|
4874
5308
|
return h("span", { className: "smem-sum-item", key: "sum-" + label }, [
|
|
4875
5309
|
label,
|
|
4876
|
-
h("button", { type: "button", className: "smem-sum-x", key: "x", title: "
|
|
5310
|
+
h("button", { type: "button", className: "smem-sum-x", key: "x", title: t("filter.remove"), onClick: onRemove }, "×"),
|
|
4877
5311
|
]);
|
|
4878
5312
|
}
|
|
4879
5313
|
// 两个计数永远一起露脸:用户不必先筛状态才知道档案馆里有没有东西。
|
|
4880
|
-
const headerCount =
|
|
4881
|
-
|
|
5314
|
+
const headerCount = hasFilter
|
|
5315
|
+
? t("header.countsFiltered", { shown: filtered.length, total: statusItems.length, active: items.length, archived: archiveItems.length })
|
|
5316
|
+
: t("header.counts", { active: items.length, archived: archiveItems.length });
|
|
4882
5317
|
// 头部带的「待重启生效」徽标:有多少项要重启才生效,从 limits / pendingLimits 的差算出来
|
|
4883
5318
|
// (reserved 名单的变化 getSettings 只给 restartRequired 布尔值,数不出来就不写数)。
|
|
4884
5319
|
const LIMIT_KEYS = ["maxResults", "maxChars", "maxBaseline", "maxSessionBytes", "staleDays"];
|
|
@@ -4909,39 +5344,39 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4909
5344
|
|
|
4910
5345
|
return h("div", { className: "smem-root" }, [
|
|
4911
5346
|
h("div", { className: "smem-tabs", key: "tabs", role: "tablist" }, [
|
|
4912
|
-
h("button", { type: "button", className: "smem-tab" + (tab === "files" ? " smem-tab--on" : ""), key: "f", role: "tab", "aria-selected": tab === "files" ? "true" : "false", onClick: function () { setTab("files"); } }, "
|
|
4913
|
-
h("button", { type: "button", className: "smem-tab" + (tab === "star" ? " smem-tab--on" : ""), key: "s", role: "tab", "aria-selected": tab === "star" ? "true" : "false", onClick: function () { setTab("star"); } }, "
|
|
4914
|
-
h("button", { type: "button", className: "smem-tab" + (tab === "autodream" ? " smem-tab--on" : ""), key: "d", role: "tab", "aria-selected": tab === "autodream" ? "true" : "false", onClick: function () { setTab("autodream"); } }, "
|
|
5347
|
+
h("button", { type: "button", className: "smem-tab" + (tab === "files" ? " smem-tab--on" : ""), key: "f", role: "tab", "aria-selected": tab === "files" ? "true" : "false", onClick: function () { setTab("files"); } }, t("tab.files")),
|
|
5348
|
+
h("button", { type: "button", className: "smem-tab" + (tab === "star" ? " smem-tab--on" : ""), key: "s", role: "tab", "aria-selected": tab === "star" ? "true" : "false", onClick: function () { setTab("star"); } }, t("tab.star")),
|
|
5349
|
+
h("button", { type: "button", className: "smem-tab" + (tab === "autodream" ? " smem-tab--on" : ""), key: "d", role: "tab", "aria-selected": tab === "autodream" ? "true" : "false", onClick: function () { setTab("autodream"); } }, t("tab.autodream")),
|
|
4915
5350
|
// 视觉分组:左边「看」(文件列表 / 星图),右边「设置 · 自动化」。
|
|
4916
5351
|
h("span", { className: "smem-tabs-sep", key: "sep", "aria-hidden": "true" }),
|
|
4917
5352
|
// 「自动归档」排在自动做梦之后:归档是**确定性规则**(按闲置天数算,不调模型),
|
|
4918
5353
|
// 和「让模型整理记忆」是两件事。塞在自动做梦的设置里用户找不到
|
|
4919
5354
|
// (2026-10-11 用户原话:「扔这里连我都找不到」)。
|
|
4920
|
-
h("button", { type: "button", className: "smem-tab" + (tab === "archive" ? " smem-tab--on" : ""), key: "ar", role: "tab", "aria-selected": tab === "archive" ? "true" : "false", onClick: function () { setTab("archive"); } }, "
|
|
5355
|
+
h("button", { type: "button", className: "smem-tab" + (tab === "archive" ? " smem-tab--on" : ""), key: "ar", role: "tab", "aria-selected": tab === "archive" ? "true" : "false", onClick: function () { setTab("archive"); } }, t("tab.archive")),
|
|
4921
5356
|
// 体检再排在「自动归档」之后、独立成一屏:它只报告不修改,和「文件列表」里的
|
|
4922
5357
|
// 增删改是两件事,塞在工具抽屉里会让人以为它是个动作按钮。
|
|
4923
|
-
h("button", { type: "button", className: "smem-tab" + (tab === "audit" ? " smem-tab--on" : ""), key: "a", role: "tab", "aria-selected": tab === "audit" ? "true" : "false", onClick: openAuditTab }, "
|
|
5358
|
+
h("button", { type: "button", className: "smem-tab" + (tab === "audit" ? " smem-tab--on" : ""), key: "a", role: "tab", "aria-selected": tab === "audit" ? "true" : "false", onClick: openAuditTab }, t("tab.audit")),
|
|
4924
5359
|
]),
|
|
4925
5360
|
// 四个 tab 共用的一条头部带:我在哪 · 目录状态 · 活动/归档计数 · 体检徽标 · 待重启徽标 · 主操作。
|
|
4926
5361
|
// 层级形状固定:导航=下划线(.smem-tabs)、筛选=chip、动作=按钮,三种不混用。
|
|
4927
5362
|
h("div", { className: "smem-head", key: "head" }, [
|
|
4928
|
-
h("span", { className: "smem-title", key: "t" }, "
|
|
5363
|
+
h("span", { className: "smem-title", key: "t" }, t("section.label")),
|
|
4929
5364
|
// 目录状态收进状态点的 title(不可读时下面另有一条 smem-err,错误本来就该显眼)。
|
|
4930
|
-
h("span", { className: "smem-dot" + (dirOk ? "" : " smem-dot--bad"), key: "dot", title: (dirOk ? "
|
|
5365
|
+
h("span", { className: "smem-dot" + (dirOk ? "" : " smem-dot--bad"), key: "dot", title: t("header.dirTitle", { state: dirOk ? t("header.dirReadable") : t("header.dirUnreadable"), tab: tabLabel(tab) }) }),
|
|
4931
5366
|
h("span", { className: "smem-badge", key: "cnt" }, headerCount),
|
|
4932
5367
|
// 体检徽标:**抽屉关着也一直看得见**,点它就切到「体检」那一屏。
|
|
4933
5368
|
h("button", {
|
|
4934
5369
|
type: "button", key: "aud",
|
|
4935
5370
|
className: "smem-tool-badge" + (audit && audit.problems ? " smem-tool-badge--bad" : ""),
|
|
4936
|
-
title: audit ? (audit.problems ? "
|
|
5371
|
+
title: audit ? (audit.problems ? t("header.auditSwitch") : t("header.auditClean")) : t("header.auditRun"),
|
|
4937
5372
|
onClick: openAuditTab,
|
|
4938
|
-
}, audit ? (audit.problems ? ("
|
|
5373
|
+
}, audit ? (audit.problems ? t("header.auditProblems", { count: audit.problems }) : t("header.auditPass")) : t("header.auditNotRun")),
|
|
4939
5374
|
restartPending
|
|
4940
5375
|
? h("button", {
|
|
4941
5376
|
type: "button", key: "rs", className: "smem-tool-badge smem-ad-badge--warn",
|
|
4942
|
-
title: "
|
|
5377
|
+
title: t("header.restartTitle"),
|
|
4943
5378
|
onClick: function () { setTab("autodream"); setAdSeg("settings"); },
|
|
4944
|
-
}, (pendingDiff ?
|
|
5379
|
+
}, t(pendingDiff ? "header.restartPendingCount" : "header.restartPending", { count: pendingDiff }))
|
|
4945
5380
|
: null,
|
|
4946
5381
|
// 主操作:只放当前 tab 的那一个(星图/自动做梦的主操作在各自面板里,不重复摆一份)。
|
|
4947
5382
|
// 弹簧:把主操作顶到右边。**换行时它留在上一行**,主操作于是落到下一行、
|
|
@@ -4949,30 +5384,30 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4949
5384
|
// 也推到最右,看起来像"跑出格子了")。
|
|
4950
5385
|
h("span", { className: "smem-head-spacer", key: "sp" }),
|
|
4951
5386
|
h("span", { className: "smem-head-actions", key: "act" }, tab === "files" ? [
|
|
4952
|
-
h("button", { type: "button", className: "smem-btn", key: "open", disabled: busy || !dirOk, title: "
|
|
4953
|
-
h("button", { type: "button", className: "smem-btn", key: "refresh", disabled: busy || loading, onClick: load }, "
|
|
4954
|
-
h("button", { type: "button", className: "smem-btn smem-btn-primary", key: "add", onClick: openAdd }, "
|
|
5387
|
+
h("button", { type: "button", className: "smem-btn", key: "open", disabled: busy || !dirOk, title: t("header.openFolderTitle"), onClick: openMemoryFolder }, t("header.openFolder")),
|
|
5388
|
+
h("button", { type: "button", className: "smem-btn", key: "refresh", disabled: busy || loading, onClick: load }, t("header.refresh")),
|
|
5389
|
+
h("button", { type: "button", className: "smem-btn smem-btn-primary", key: "add", onClick: openAdd }, t("action.add")),
|
|
4955
5390
|
] : null),
|
|
4956
5391
|
]),
|
|
4957
5392
|
// 更新提示:四个 tab 共用一条,只在真有新版时出现;「×」只是这次不看
|
|
4958
5393
|
updateInfo
|
|
4959
5394
|
? h("div", { className: "smem-update", key: "update" }, [
|
|
4960
|
-
h("span", { key: "t" }, "
|
|
5395
|
+
h("span", { key: "t" }, t("update.found", { latest: updateInfo.latest, current: updateInfo.current })),
|
|
4961
5396
|
h("a", {
|
|
4962
5397
|
key: "l", className: "smem-update-link",
|
|
4963
5398
|
href: updateInfo.releasesUrl || "https://github.com/gezi-wen/sage-mem/releases",
|
|
4964
5399
|
target: "_blank", rel: "noreferrer",
|
|
4965
|
-
}, "
|
|
5400
|
+
}, t("update.notes")),
|
|
4966
5401
|
h("button", {
|
|
4967
5402
|
type: "button", key: "x", className: "smem-update-x",
|
|
4968
|
-
title: "
|
|
5403
|
+
title: t("update.dismissTitle"),
|
|
4969
5404
|
onClick: function () { setUpdateInfo(null); },
|
|
4970
5405
|
}, "×"),
|
|
4971
5406
|
])
|
|
4972
5407
|
: null,
|
|
4973
5408
|
// 工具栏只剩「搜索 + 工具 ▾」:刷新与添加已经收进头部带的主操作位。
|
|
4974
5409
|
tab === "files" ? h("div", { className: "smem-toolbar", key: "bar" }, [
|
|
4975
|
-
h("input", { className: "smem-search", key: "q", value: query, placeholder: "
|
|
5410
|
+
h("input", { className: "smem-search", key: "q", value: query, placeholder: t("toolbar.search"), onChange: function (e) { setQuery(e.target.value); } }),
|
|
4976
5411
|
// 工具抽屉:关着时这里只有一个按钮,零占位;面板一次只开一个。
|
|
4977
5412
|
// `data-smem-drawer` 是「点外部关闭」的排除标记:这一整块里的点击
|
|
4978
5413
|
// 都算「抽屉内部」,包括开抽屉的那一次点击本身(见上面 effect 的长注释)。
|
|
@@ -4982,27 +5417,27 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
4982
5417
|
className: "smem-btn" + (drawer ? " smem-btn-primary" : ""),
|
|
4983
5418
|
"aria-expanded": drawer ? "true" : "false",
|
|
4984
5419
|
"aria-haspopup": "menu",
|
|
4985
|
-
title:
|
|
5420
|
+
title: t(dbgOn ? "tools.titleDebug" : "tools.title"),
|
|
4986
5421
|
onClick: function () { setDrawer(!drawer); },
|
|
4987
|
-
}, "
|
|
5422
|
+
}, t("tools.button")),
|
|
4988
5423
|
drawer ? h("div", { className: "smem-drawer-pop", key: "pop" }, [
|
|
4989
5424
|
// 体检独立成 tab 之后,抽屉里只剩这两件「要动手」的事。
|
|
4990
5425
|
h("button", {
|
|
4991
5426
|
type: "button", className: "smem-drawer-item", key: "c",
|
|
4992
5427
|
"data-on": tool === "candidates" ? "1" : "0",
|
|
4993
5428
|
onClick: function () { toggleTool("candidates"); setDrawer(false); },
|
|
4994
|
-
}, ["
|
|
5429
|
+
}, [t("tools.candidates"), h("span", { className: "smem-status", key: "s" }, cand ? t("tools.candidatesCount", { count: cand.count }) : t("tools.candidatesEmpty"))]),
|
|
4995
5430
|
h("button", {
|
|
4996
5431
|
type: "button", className: "smem-drawer-item", key: "r",
|
|
4997
5432
|
"data-on": tool === "raw" ? "1" : "0",
|
|
4998
5433
|
onClick: function () { toggleTool("raw"); setDrawer(false); },
|
|
4999
|
-
}, ["
|
|
5434
|
+
}, [t("tools.raw"), h("span", { className: "smem-status", key: "s" }, settings && settings.reserved ? t("tools.rawCount", { count: settings.reserved.length }) : "")]),
|
|
5000
5435
|
// 1.7.0:只在调试开关打开时出现(关着时抽屉仍是两项,界面零变化)
|
|
5001
5436
|
dbgOn ? h("button", {
|
|
5002
5437
|
type: "button", className: "smem-drawer-item", key: "d",
|
|
5003
5438
|
"data-on": tool === "debug" ? "1" : "0",
|
|
5004
5439
|
onClick: function () { toggleTool("debug"); setDrawer(false); },
|
|
5005
|
-
}, ["
|
|
5440
|
+
}, [t("tools.debug"), h("span", { className: "smem-status", key: "s" }, dbg && Array.isArray(dbg.records) ? t("tools.debugCount", { count: dbg.records.length }) : "")]) : null,
|
|
5006
5441
|
]) : null,
|
|
5007
5442
|
]),
|
|
5008
5443
|
]) : null,
|
|
@@ -5080,25 +5515,25 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
5080
5515
|
notice && !error ? h("div", { className: "smem-ok", key: "ok", role: "status" }, "✓ " + notice) : null,
|
|
5081
5516
|
formOpen
|
|
5082
5517
|
? h("div", { className: "smem-form", key: "form" }, [
|
|
5083
|
-
h("div", { className: "smem-form-title", key: "ft" }, editingFile ? "
|
|
5518
|
+
h("div", { className: "smem-form-title", key: "ft" }, editingFile ? t("form.editTitle", { file: editingFile }) : t("form.addTitle")),
|
|
5084
5519
|
h("div", { className: "smem-row", key: "cat" }, [
|
|
5085
|
-
h("span", { className: "smem-label", key: "l" }, "
|
|
5086
|
-
h("div", { className: "smem-chips", key: "chips" }, TYPE_ORDER.map(function (
|
|
5087
|
-
const meta = TYPE_META[
|
|
5088
|
-
return h(Chip, { key:
|
|
5520
|
+
h("span", { className: "smem-label", key: "l" }, t("form.type")),
|
|
5521
|
+
h("div", { className: "smem-chips", key: "chips" }, TYPE_ORDER.map(function (id) {
|
|
5522
|
+
const meta = TYPE_META[id];
|
|
5523
|
+
return h(Chip, { key: id, label: meta[0] + " " + typeLabel(id), on: formType === id, onClick: function () { setFormType(id); } });
|
|
5089
5524
|
})),
|
|
5090
5525
|
]),
|
|
5091
|
-
h("label", { className: "smem-label", key: "ln" }, "
|
|
5092
|
-
h("input", { className: "smem-input", key: "tn", value: formName, onChange: function (e) { setFormName(e.target.value); }, placeholder: "
|
|
5093
|
-
h("label", { className: "smem-label", key: "ld" }, "
|
|
5094
|
-
h("input", { className: "smem-input", key: "td", value: formDesc, onChange: function (e) { setFormDesc(e.target.value); }, placeholder: "
|
|
5095
|
-
h("label", { className: "smem-label", key: "lt" }, "
|
|
5096
|
-
h("input", { className: "smem-input", key: "tt", value: formTags, onChange: function (e) { setFormTags(e.target.value); }, placeholder: "
|
|
5097
|
-
h("label", { className: "smem-label", key: "lb" }, "
|
|
5098
|
-
h("textarea", { className: "smem-textarea", key: "tb", rows: 6, value: formBody, onChange: function (e) { setFormBody(e.target.value); }, placeholder: "
|
|
5526
|
+
h("label", { className: "smem-label", key: "ln" }, t("form.fileName", { type: formType })),
|
|
5527
|
+
h("input", { className: "smem-input", key: "tn", value: formName, onChange: function (e) { setFormName(e.target.value); }, placeholder: t("form.fileNamePlaceholder") }),
|
|
5528
|
+
h("label", { className: "smem-label", key: "ld" }, t("form.desc")),
|
|
5529
|
+
h("input", { className: "smem-input", key: "td", value: formDesc, onChange: function (e) { setFormDesc(e.target.value); }, placeholder: t("form.descPlaceholder") }),
|
|
5530
|
+
h("label", { className: "smem-label", key: "lt" }, t("form.tags")),
|
|
5531
|
+
h("input", { className: "smem-input", key: "tt", value: formTags, onChange: function (e) { setFormTags(e.target.value); }, placeholder: t("form.tagsPlaceholder") }),
|
|
5532
|
+
h("label", { className: "smem-label", key: "lb" }, t("form.body")),
|
|
5533
|
+
h("textarea", { className: "smem-textarea", key: "tb", rows: 6, value: formBody, onChange: function (e) { setFormBody(e.target.value); }, placeholder: t("form.bodyPlaceholder") }),
|
|
5099
5534
|
h("div", { className: "smem-row", key: "btns" }, [
|
|
5100
|
-
h("button", { type: "button", className: "smem-btn smem-btn-primary", key: "save", disabled: busy, onClick: submit }, busy ? "
|
|
5101
|
-
h("button", { type: "button", className: "smem-btn", key: "cancel", disabled: busy, onClick: closeForm }, "
|
|
5535
|
+
h("button", { type: "button", className: "smem-btn smem-btn-primary", key: "save", disabled: busy, onClick: submit }, busy ? t("card.saving") : t("card.save")),
|
|
5536
|
+
h("button", { type: "button", className: "smem-btn", key: "cancel", disabled: busy, onClick: closeForm }, t("card.cancel")),
|
|
5102
5537
|
]),
|
|
5103
5538
|
])
|
|
5104
5539
|
: null,
|
|
@@ -5131,32 +5566,136 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
5131
5566
|
/** 该槽位的清理句柄数组(null = 当前没注册)。 */
|
|
5132
5567
|
let sidebarDispose = null;
|
|
5133
5568
|
|
|
5134
|
-
/**
|
|
5569
|
+
/**
|
|
5570
|
+
* 侧栏图标:内联 SVG、currentColor、细线 —— 不引外部图片/字体,跟随宿主明暗主题。
|
|
5571
|
+
*
|
|
5572
|
+
* 1.8.2 起字形与仓库根的 `icon.png` 同源:**叠层记忆卡片**(后面一张只描边、低透明,
|
|
5573
|
+
* 前面一张压在左下),不再是「文档三行」。单色而不是品牌金:侧栏图标必须跟随
|
|
5574
|
+
* 宿主的 currentColor,深色 / 浅色主题下才都可读。
|
|
5575
|
+
*/
|
|
5135
5576
|
function SidebarEntryIcon(props) {
|
|
5136
5577
|
const edge = typeof props.size === "number" && props.size > 0 ? props.size : 16;
|
|
5137
5578
|
return h("svg", {
|
|
5138
5579
|
width: edge, height: edge, viewBox: "0 0 16 16", fill: "none",
|
|
5139
5580
|
"aria-hidden": "true", focusable: "false",
|
|
5140
5581
|
}, [
|
|
5582
|
+
// 后面那张卡:错开、只描边、低透明 —— 表达「一叠记忆」而不是两张孤立的卡。
|
|
5141
5583
|
h("rect", {
|
|
5142
|
-
key: "
|
|
5143
|
-
stroke: "currentColor", strokeWidth: "1.2",
|
|
5584
|
+
key: "back", x: "4.4", y: "2.2", width: "9.4", height: "9", rx: "1.8",
|
|
5585
|
+
stroke: "currentColor", strokeWidth: "1.2", opacity: "0.45",
|
|
5144
5586
|
}),
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5587
|
+
// 前面那张卡:压在左下,实心描边。
|
|
5588
|
+
h("rect", {
|
|
5589
|
+
key: "front", x: "2.2", y: "4.8", width: "9.4", height: "9", rx: "1.8",
|
|
5590
|
+
stroke: "currentColor", strokeWidth: "1.2",
|
|
5148
5591
|
}),
|
|
5149
5592
|
h("path", {
|
|
5150
|
-
key: "
|
|
5593
|
+
key: "l1", d: "M4.7 8.2h4.4",
|
|
5151
5594
|
stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round",
|
|
5152
5595
|
}),
|
|
5153
5596
|
h("path", {
|
|
5154
|
-
key: "
|
|
5597
|
+
key: "l2", d: "M4.7 10.6h3.1",
|
|
5155
5598
|
stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round",
|
|
5156
5599
|
}),
|
|
5157
5600
|
]);
|
|
5158
5601
|
}
|
|
5159
5602
|
|
|
5603
|
+
/** 卡片里的错误文案:remote 可能 reject 一个字符串 / 对象 / Error,尽量展开成人话。 */
|
|
5604
|
+
function pluginCardErr(e) {
|
|
5605
|
+
if (!e) return t("pluginCard.saveFailed");
|
|
5606
|
+
if (e instanceof Error) return e.message || String(e);
|
|
5607
|
+
try {
|
|
5608
|
+
const s = JSON.stringify(e);
|
|
5609
|
+
return s && s !== "{}" ? s.slice(0, 200) : String(e);
|
|
5610
|
+
} catch (_) {
|
|
5611
|
+
return String(e);
|
|
5612
|
+
}
|
|
5613
|
+
}
|
|
5614
|
+
|
|
5615
|
+
/**
|
|
5616
|
+
* 插件详情页的「侧栏入口」卡片(1.8.2)。
|
|
5617
|
+
*
|
|
5618
|
+
* 槽位 `plugins.bundle.config` 是 keyed 槽(key = 包名),宿主在 sage-mem 的插件页里、
|
|
5619
|
+
* description 与「包含的组件」行之间渲染它,ownerProps 为 `{ view: "summary" | "page" }`:
|
|
5620
|
+
* - summary:标题下的一块摘要位 —— 这里返回 null,不在摘要位重复渲染;
|
|
5621
|
+
* - page:详情页本体(本卡片自己写设置,所以宿主给的是 page)。
|
|
5622
|
+
*
|
|
5623
|
+
* 数据与设置页同一个来源(remote.memory 的 getSettings / setSettings),切换成功后调
|
|
5624
|
+
* 模块级 `setSidebarEntry`:**立即**增删侧栏图标,不需要刷新 / 重启。
|
|
5625
|
+
* 勾选态一律由服务端回传的 settings 派生 —— 失败时什么都不改,于是自动回到原值。
|
|
5626
|
+
*/
|
|
5627
|
+
function SidebarEntryConfigCard(props) {
|
|
5628
|
+
const ctx = props && props.ctx;
|
|
5629
|
+
// 必须走 stableRemote:真实宿主里 ctx.get() 每次返回新对象,进依赖数组会无限重跑。
|
|
5630
|
+
const remote = stableRemote(ctx, "remote.memory");
|
|
5631
|
+
const sOn = react.useState(true); const on = sOn[0], setOn = sOn[1];
|
|
5632
|
+
const sBusy = react.useState(false); const busy = sBusy[0], setBusy = sBusy[1];
|
|
5633
|
+
const sErr = react.useState(""); const err = sErr[0], setErr = sErr[1];
|
|
5634
|
+
|
|
5635
|
+
react.useEffect(function () {
|
|
5636
|
+
if (!remote || typeof remote.getSettings !== "function") return undefined;
|
|
5637
|
+
let alive = true;
|
|
5638
|
+
Promise.resolve(remote.getSettings()).then(function (out) {
|
|
5639
|
+
if (!alive) return;
|
|
5640
|
+
const loaded = unwrap(out) || {};
|
|
5641
|
+
// 旧宿主没有 ui 字段 / 值不是布尔 → 保持默认「开」,不弹错、不阻塞界面。
|
|
5642
|
+
if (loaded.ui && typeof loaded.ui.sidebarEntry === "boolean") setOn(loaded.ui.sidebarEntry);
|
|
5643
|
+
}, function () {
|
|
5644
|
+
// 读设置失败也按「开」显示:这里只是插件页的一张卡片,不该因为它报红。
|
|
5645
|
+
});
|
|
5646
|
+
return function () { alive = false; };
|
|
5647
|
+
}, [remote]);
|
|
5648
|
+
|
|
5649
|
+
// summary 位不渲染(hooks 必须在上方无条件调用完再返回,规则与顺序都不变)。
|
|
5650
|
+
if (props && props.view === "summary") return null;
|
|
5651
|
+
|
|
5652
|
+
function toggle(next) {
|
|
5653
|
+
if (busy) return;
|
|
5654
|
+
if (!remote || typeof remote.setSettings !== "function") {
|
|
5655
|
+
setErr(t("pluginCard.noSetSettings"));
|
|
5656
|
+
return;
|
|
5657
|
+
}
|
|
5658
|
+
setBusy(true); setErr("");
|
|
5659
|
+
Promise.resolve(remote.setSettings({ ui: { sidebarEntry: !!next } })).then(function (out) {
|
|
5660
|
+
setBusy(false);
|
|
5661
|
+
// setSettings 是 { ok, settings, restartRequired } 形状,不要过 unwrap。
|
|
5662
|
+
if (out && out.ok === false) {
|
|
5663
|
+
setErr(out.error || t("error.saveRejected"));
|
|
5664
|
+
return;
|
|
5665
|
+
}
|
|
5666
|
+
const settings = out && out.settings ? out.settings : null;
|
|
5667
|
+
const applied = settings && settings.ui && typeof settings.ui.sidebarEntry === "boolean"
|
|
5668
|
+
? settings.ui.sidebarEntry
|
|
5669
|
+
: !!next;
|
|
5670
|
+
setOn(applied);
|
|
5671
|
+
// 写盘成功之后才动槽位:设置没落盘时界面不该先变一个样。
|
|
5672
|
+
setSidebarEntry(!!next);
|
|
5673
|
+
}, function (e) {
|
|
5674
|
+
setBusy(false);
|
|
5675
|
+
setErr(pluginCardErr(e));
|
|
5676
|
+
});
|
|
5677
|
+
}
|
|
5678
|
+
|
|
5679
|
+
return h("div", { className: "smem-plugin-card" }, [
|
|
5680
|
+
h("div", { className: "smem-plugin-card-head", key: "h" }, [
|
|
5681
|
+
h("span", { className: "smem-plugin-card-title", key: "t" }, t("pluginCard.title")),
|
|
5682
|
+
h("span", { className: "smem-plugin-card-status", key: "s" }, on ? t("pluginCard.on") : t("pluginCard.off")),
|
|
5683
|
+
]),
|
|
5684
|
+
h("label", { className: "smem-check smem-plugin-card-check", key: "row" }, [
|
|
5685
|
+
h("input", {
|
|
5686
|
+
type: "checkbox", key: "c",
|
|
5687
|
+
checked: on,
|
|
5688
|
+
disabled: busy,
|
|
5689
|
+
onChange: function (e) { toggle(!!(e.target && e.target.checked)); },
|
|
5690
|
+
}),
|
|
5691
|
+
h("span", { key: "l" }, t("pluginCard.label")),
|
|
5692
|
+
]),
|
|
5693
|
+
h("div", { className: "smem-plugin-card-note", key: "n" },
|
|
5694
|
+
t("pluginCard.note")),
|
|
5695
|
+
err ? h("div", { className: "smem-plugin-card-err", key: "e" }, err) : null,
|
|
5696
|
+
]);
|
|
5697
|
+
}
|
|
5698
|
+
|
|
5160
5699
|
/**
|
|
5161
5700
|
* 返回会话:宿主 layout 服务的 `selectPanel(null)` 就是把中央主区还给 Conversation。
|
|
5162
5701
|
* 取不到 layout(旧外壳 / 测试环境)时是**空操作**,不影响页面其它部分。
|
|
@@ -5177,7 +5716,7 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
5177
5716
|
h("div", { className: "smem-page-top", key: "top" }, [
|
|
5178
5717
|
h("button", {
|
|
5179
5718
|
type: "button", className: "smem-page-back", key: "back",
|
|
5180
|
-
title: "
|
|
5719
|
+
title: t("page.back"), onClick: onBack,
|
|
5181
5720
|
}, [
|
|
5182
5721
|
h("svg", {
|
|
5183
5722
|
key: "i", width: 14, height: 14, viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true",
|
|
@@ -5185,7 +5724,7 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
5185
5724
|
d: "M10 3.5 5.5 8l4.5 4.5", stroke: "currentColor", strokeWidth: "1.6",
|
|
5186
5725
|
strokeLinecap: "round", strokeLinejoin: "round",
|
|
5187
5726
|
})),
|
|
5188
|
-
h("span", { key: "l" }, "
|
|
5727
|
+
h("span", { key: "l" }, t("page.back")),
|
|
5189
5728
|
]),
|
|
5190
5729
|
]),
|
|
5191
5730
|
h(Section, { key: "sec", ...props, ctx: sidebarCtx }),
|
|
@@ -5203,7 +5742,7 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
5203
5742
|
name: "sidebar.panellist",
|
|
5204
5743
|
id: SIDEBAR_ENTRY_ID,
|
|
5205
5744
|
order: 20,
|
|
5206
|
-
label: "
|
|
5745
|
+
label: () => t("sidebar.label"),
|
|
5207
5746
|
}, SidebarEntryIcon);
|
|
5208
5747
|
}));
|
|
5209
5748
|
// 中央主区 keyed 槽:key 与侧栏 id 相同;inject 面把「返回会话」交给页面组件。
|
|
@@ -5258,13 +5797,14 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
5258
5797
|
tag.textContent = CSS;
|
|
5259
5798
|
document.head.appendChild(tag);
|
|
5260
5799
|
}
|
|
5800
|
+
setupI18n(ctx);
|
|
5261
5801
|
ctx.slots.inject("settings.section", () =>
|
|
5262
5802
|
ctx.slots.register(
|
|
5263
5803
|
{
|
|
5264
5804
|
name: "settings.section",
|
|
5265
5805
|
id: "sage-mem",
|
|
5266
5806
|
order: 20,
|
|
5267
|
-
label: "
|
|
5807
|
+
label: () => t("section.label"),
|
|
5268
5808
|
// 声明「记忆星图」子槽:sage-starmap 向它注入星空视图,
|
|
5269
5809
|
// 记忆管理页用 renderSlot 渲染。声明 children 后 shell 才会
|
|
5270
5810
|
// 向本 section 组件传入 renderSlot。
|
|
@@ -5288,6 +5828,29 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
|
|
|
5288
5828
|
sidebarEnabled = true;
|
|
5289
5829
|
}
|
|
5290
5830
|
setSidebarEntry(sidebarEnabled);
|
|
5831
|
+
|
|
5832
|
+
// ── 1.8.2 插件详情页的「侧栏入口」卡片 ──────────────────────────────
|
|
5833
|
+
// `plugins.bundle.config` 是 keyed 槽,key = 本 bundle 的 **package name**(sage-mem)。
|
|
5834
|
+
// 宿主在插件页的 description 与「包含的组件」行之间渲染它;ownerProps 形如
|
|
5835
|
+
// `{ view: "summary" | "page" }`,卡片里自己再判一次(summary 返回 null)。
|
|
5836
|
+
//
|
|
5837
|
+
// ⚠️ 这个槽 0.1.7+ 才有。旧宿主上 inject 会**抛错**,所以必须整段 try/catch ——
|
|
5838
|
+
// 否则一个可选的插件页卡片会把设置页与侧栏入口一起拖垮(整个 client 插件不 apply)。
|
|
5839
|
+
// 放在 apply 最后:即使这里因宿主差异出问题,前面的界面已经挂上去了。
|
|
5840
|
+
try {
|
|
5841
|
+
if (ctx.slots && typeof ctx.slots.inject === "function") {
|
|
5842
|
+
ctx.slots.inject("plugins.bundle.config", function () {
|
|
5843
|
+
return ctx.slots.register({
|
|
5844
|
+
name: "plugins.bundle.config",
|
|
5845
|
+
key: "sage-mem",
|
|
5846
|
+
}, function (props) {
|
|
5847
|
+
return react.createElement(SidebarEntryConfigCard, { ...(props || {}), ctx });
|
|
5848
|
+
});
|
|
5849
|
+
});
|
|
5850
|
+
}
|
|
5851
|
+
} catch (e) {
|
|
5852
|
+
// 旧宿主没有这个槽 / 不接受注册:静默跳过,绝不影响其余界面。
|
|
5853
|
+
}
|
|
5291
5854
|
}
|
|
5292
5855
|
|
|
5293
5856
|
exports.apply = apply;
|