dsh-prime-memory 0.20.0-beta.2 → 0.20.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +192 -3
- package/ENGINEERING-NOTES.md +17 -0
- package/dist/client.js +1158 -405
- package/dist/config.d.ts +0 -120
- package/dist/config.js +1 -21
- package/dist/contract.d.ts +111 -56
- package/dist/hooks/recall.d.ts +4 -1
- package/dist/hooks/recall.js +7 -1
- package/dist/index.d.ts +0 -78
- package/dist/index.js +14 -21
- package/dist/metadata-validators.d.ts +5 -0
- package/dist/metadata-validators.js +11 -1
- package/dist/pipeline/recluster.js +1 -1
- package/dist/pipeline/ruminate.d.ts +11 -1
- package/dist/pipeline/ruminate.js +18 -1
- package/dist/room-review.d.ts +196 -0
- package/dist/room-review.js +281 -0
- package/dist/stats.d.ts +23 -34
- package/dist/stats.js +268 -201
- package/dist/store/l1.d.ts +34 -0
- package/dist/store/l1.js +68 -1
- package/dist/store/recall-usage.d.ts +43 -0
- package/dist/store/recall-usage.js +149 -0
- package/dist/store/rooms-registry.d.ts +66 -0
- package/dist/store/rooms-registry.js +250 -0
- package/dist/store/session-modes.d.ts +0 -8
- package/dist/store/session-modes.js +0 -52
- package/dist/store/sqlite.d.ts +4 -0
- package/dist/store/sqlite.js +20 -0
- package/dist/tools/index.d.ts +4 -1
- package/dist/tools/index.js +232 -1
- package/dist/types.d.ts +4 -0
- package/dsh.plugin.json +1 -1
- package/package.json +1 -1
- package/dist/hooks/long-task.d.ts +0 -31
- package/dist/hooks/long-task.js +0 -93
- package/dist/store/context-usage.d.ts +0 -28
- package/dist/store/context-usage.js +0 -47
- package/dist/store/todo-ref.d.ts +0 -41
- package/dist/store/todo-ref.js +0 -65
package/dist/client.js
CHANGED
|
@@ -32,7 +32,7 @@ var __defProp = Object.defineProperty;
|
|
|
32
32
|
module.exports = __toCommonJS(entry_exports);
|
|
33
33
|
|
|
34
34
|
// client/src/panel.tsx
|
|
35
|
-
var
|
|
35
|
+
var import_react19 = require("react");
|
|
36
36
|
|
|
37
37
|
// client/src/sidebar-icon.ts
|
|
38
38
|
var BOOK_ICON_SVG = '<svg data-mem-icon="1" viewBox="0 0 16 16" width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg" style="flex-shrink:0"><path d="M8 3.4C6.6 2.5 4.6 2.4 2.9 3.1v9.3c1.7-.7 3.7-.6 5.1.3 1.4-.9 3.4-1 5.1-.3V3.1C11.4 2.4 9.4 2.5 8 3.4Z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/><path d="M8 3.4v9.3" stroke="currentColor" stroke-width="1.2"/></svg>';
|
|
@@ -1645,8 +1645,8 @@ var __defProp = Object.defineProperty;
|
|
|
1645
1645
|
setOpen(false);
|
|
1646
1646
|
setIdx(-1);
|
|
1647
1647
|
if (refocus && wrapRef.current) {
|
|
1648
|
-
const
|
|
1649
|
-
if (
|
|
1648
|
+
const btn3 = wrapRef.current.querySelector("button");
|
|
1649
|
+
if (btn3 && btn3.focus) btn3.focus();
|
|
1650
1650
|
}
|
|
1651
1651
|
};
|
|
1652
1652
|
const pick = (id) => {
|
|
@@ -3287,10 +3287,11 @@ var __defProp = Object.defineProperty;
|
|
|
3287
3287
|
const [sceneFilter, setSceneFilter] = (0, import_react15.useState)("");
|
|
3288
3288
|
const [hallFilter, setHallFilter] = (0, import_react15.useState)([]);
|
|
3289
3289
|
const [tagFilter, setTagFilter] = (0, import_react15.useState)("");
|
|
3290
|
+
const [untaggedOnly, setUntaggedOnly] = (0, import_react15.useState)(false);
|
|
3291
|
+
const [orphanCount, setOrphanCount] = (0, import_react15.useState)(0);
|
|
3290
3292
|
const [retiredFilter, setRetiredFilter] = (0, import_react15.useState)("");
|
|
3291
|
-
const [rooms, setRooms] = (0, import_react15.useState)([]);
|
|
3292
3293
|
const [wingCatalog, setHallCatalog] = (0, import_react15.useState)(null);
|
|
3293
|
-
const [last, setLast] = (0, import_react15.useState)({ query: "", type: "", scene: "", halls: [], tag: "", retired: "" });
|
|
3294
|
+
const [last, setLast] = (0, import_react15.useState)({ query: "", type: "", scene: "", halls: [], tag: "", untagged: false, retired: "" });
|
|
3294
3295
|
const seqRef = (0, import_react15.useRef)(0);
|
|
3295
3296
|
const fetchPage = (0, import_react15.useCallback)(
|
|
3296
3297
|
(conds, offset, append) => {
|
|
@@ -3303,6 +3304,7 @@ var __defProp = Object.defineProperty;
|
|
|
3303
3304
|
if (conds.scene) payload.scene = conds.scene;
|
|
3304
3305
|
if (conds.halls.length > 0) payload.halls = conds.halls;
|
|
3305
3306
|
if (conds.tag) payload.tag = conds.tag;
|
|
3307
|
+
if (conds.untagged) payload.untagged = true;
|
|
3306
3308
|
const retiredSel = retiredSelOf(conds.retired);
|
|
3307
3309
|
if (retiredSel !== void 0) payload.retired = retiredSel;
|
|
3308
3310
|
rpc("dsh-memory/list-records", payload).then((r) => {
|
|
@@ -3314,6 +3316,7 @@ var __defProp = Object.defineProperty;
|
|
|
3314
3316
|
}
|
|
3315
3317
|
const v = r.value;
|
|
3316
3318
|
setItems((prev) => append ? prev.concat(v.items) : v.items);
|
|
3319
|
+
if (v.usageSummary) setUsageSummary(v.usageSummary);
|
|
3317
3320
|
if (!append) setSel(/* @__PURE__ */ new Set());
|
|
3318
3321
|
setHasMore(!!v.hasMore);
|
|
3319
3322
|
setTotal(v.total === void 0 || v.total === null ? null : v.total);
|
|
@@ -3329,24 +3332,24 @@ var __defProp = Object.defineProperty;
|
|
|
3329
3332
|
[rpc]
|
|
3330
3333
|
);
|
|
3331
3334
|
const search = () => {
|
|
3332
|
-
const conds = { query: query.trim(), type: typeFilter, scene: sceneFilter, halls: hallFilter, tag: tagFilter, retired: retiredFilter };
|
|
3335
|
+
const conds = { query: query.trim(), type: typeFilter, scene: sceneFilter, halls: hallFilter, tag: tagFilter, untagged: untaggedOnly, retired: retiredFilter };
|
|
3333
3336
|
setLast(conds);
|
|
3334
3337
|
fetchPage(conds, 0, false);
|
|
3335
3338
|
};
|
|
3336
3339
|
const applyRetiredFilter = (next) => {
|
|
3337
3340
|
setRetiredFilter(next);
|
|
3338
|
-
const conds = { query: query.trim(), type: typeFilter, scene: sceneFilter, halls: hallFilter, tag: tagFilter, retired: next };
|
|
3341
|
+
const conds = { query: query.trim(), type: typeFilter, scene: sceneFilter, halls: hallFilter, tag: tagFilter, untagged: untaggedOnly, retired: next };
|
|
3339
3342
|
setLast(conds);
|
|
3340
3343
|
fetchPage(conds, 0, false);
|
|
3341
3344
|
};
|
|
3342
3345
|
const loadRooms = (0, import_react15.useCallback)(() => {
|
|
3343
3346
|
rpc("dsh-memory/rooms-get", {}).then((r) => {
|
|
3344
|
-
if (r && r.ok)
|
|
3347
|
+
if (r && r.ok) setOrphanCount(r.value.orphanCount ?? 0);
|
|
3345
3348
|
}).catch(() => {
|
|
3346
3349
|
});
|
|
3347
3350
|
}, [rpc]);
|
|
3348
3351
|
(0, import_react15.useEffect)(() => {
|
|
3349
|
-
fetchPage({ query: "", type: "", scene: "", halls: [], tag: "", retired: "" }, 0, false);
|
|
3352
|
+
fetchPage({ query: "", type: "", scene: "", halls: [], tag: "", untagged: false, retired: "" }, 0, false);
|
|
3350
3353
|
loadRooms();
|
|
3351
3354
|
}, [fetchPage, loadRooms]);
|
|
3352
3355
|
const loadHiPriv = (0, import_react15.useCallback)(() => {
|
|
@@ -3449,6 +3452,7 @@ var __defProp = Object.defineProperty;
|
|
|
3449
3452
|
unknown: "已退场"
|
|
3450
3453
|
};
|
|
3451
3454
|
const countText = total !== null ? "共 " + total + " 条" : items.length + " 条" + (hasMore ? "+" : "");
|
|
3455
|
+
const [usageSummary, setUsageSummary] = (0, import_react15.useState)(null);
|
|
3452
3456
|
const selCount = sel.size;
|
|
3453
3457
|
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [
|
|
3454
3458
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: S.toolbar, children: [
|
|
@@ -3515,40 +3519,86 @@ var __defProp = Object.defineProperty;
|
|
|
3515
3519
|
}
|
|
3516
3520
|
)
|
|
3517
3521
|
] }),
|
|
3518
|
-
|
|
3519
|
-
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3522
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: { marginBottom: 10, display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }, children: [
|
|
3523
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3524
|
+
"button",
|
|
3525
|
+
{
|
|
3526
|
+
type: "button",
|
|
3527
|
+
title: untaggedOnly ? "取消孤儿筛选" : "只看没有任何 Room 绑定的记忆",
|
|
3528
|
+
onClick: () => {
|
|
3529
|
+
const next = !untaggedOnly;
|
|
3530
|
+
setUntaggedOnly(next);
|
|
3531
|
+
if (next) setTagFilter("");
|
|
3532
|
+
const conds = { query: query.trim(), type: typeFilter, scene: sceneFilter, halls: hallFilter, tag: next ? "" : tagFilter, untagged: next, retired: retiredFilter };
|
|
3533
|
+
setLast(conds);
|
|
3534
|
+
fetchPage(conds, 0, false);
|
|
3535
|
+
},
|
|
3536
|
+
style: {
|
|
3537
|
+
cursor: "pointer",
|
|
3538
|
+
fontSize: 12,
|
|
3539
|
+
padding: "2px 8px",
|
|
3540
|
+
borderRadius: 999,
|
|
3541
|
+
border: untaggedOnly ? "1px solid var(--dsh-mem-accent)" : "1px solid var(--dsh-mem-border)",
|
|
3542
|
+
background: untaggedOnly ? "var(--dsh-mem-bg-inset)" : "transparent",
|
|
3543
|
+
color: untaggedOnly ? "var(--dsh-mem-accent)" : "var(--dsh-mem-text-2)"
|
|
3544
|
+
},
|
|
3545
|
+
children: "无绑定 · " + orphanCount
|
|
3546
|
+
}
|
|
3547
|
+
),
|
|
3548
|
+
(tagFilter || untaggedOnly) && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { style: S.muted, children: [
|
|
3549
|
+
"筛选中:",
|
|
3550
|
+
untaggedOnly ? "无绑定" : tagFilter,
|
|
3551
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3523
3552
|
"button",
|
|
3524
3553
|
{
|
|
3525
3554
|
type: "button",
|
|
3526
|
-
title:
|
|
3555
|
+
title: "清除 Room/孤儿筛选",
|
|
3527
3556
|
onClick: () => {
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
const conds = { query: query.trim(), type: typeFilter, scene: sceneFilter, halls: hallFilter, tag:
|
|
3557
|
+
setTagFilter("");
|
|
3558
|
+
setUntaggedOnly(false);
|
|
3559
|
+
const conds = { query: query.trim(), type: typeFilter, scene: sceneFilter, halls: hallFilter, tag: "", untagged: false, retired: retiredFilter };
|
|
3531
3560
|
setLast(conds);
|
|
3532
3561
|
fetchPage(conds, 0, false);
|
|
3533
3562
|
},
|
|
3534
|
-
style: {
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3563
|
+
style: { cursor: "pointer", marginLeft: 6, background: "transparent", border: "none", color: "var(--dsh-mem-accent)", fontSize: 12 },
|
|
3564
|
+
children: "×清除"
|
|
3565
|
+
}
|
|
3566
|
+
)
|
|
3567
|
+
] }),
|
|
3568
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3569
|
+
"button",
|
|
3570
|
+
{
|
|
3571
|
+
type: "button",
|
|
3572
|
+
title: "导出无 Room 绑定的孤儿清单 CSV(含候选)",
|
|
3573
|
+
onClick: () => {
|
|
3574
|
+
rpc("dsh-memory/rooms-export", { kind: "orphans" }).then((r) => {
|
|
3575
|
+
if (!r || !r.ok || !r.value?.csv) return;
|
|
3576
|
+
const blob = new Blob([r.value.csv], { type: "text/csv;charset=utf-8" });
|
|
3577
|
+
const url = URL.createObjectURL(blob);
|
|
3578
|
+
const a = document.createElement("a");
|
|
3579
|
+
a.href = url;
|
|
3580
|
+
a.download = "dsh-memory-room-orphans.csv";
|
|
3581
|
+
a.click();
|
|
3582
|
+
URL.revokeObjectURL(url);
|
|
3583
|
+
}).catch(() => void 0);
|
|
3544
3584
|
},
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3585
|
+
style: {
|
|
3586
|
+
cursor: "pointer",
|
|
3587
|
+
fontSize: 12,
|
|
3588
|
+
padding: "2px 8px",
|
|
3589
|
+
borderRadius: 6,
|
|
3590
|
+
border: "1px solid var(--dsh-mem-border)",
|
|
3591
|
+
background: "transparent",
|
|
3592
|
+
color: "var(--dsh-mem-text-2)"
|
|
3593
|
+
},
|
|
3594
|
+
children: "导出孤儿"
|
|
3595
|
+
}
|
|
3596
|
+
),
|
|
3597
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: S.muted, children: "Room 标签/分类管理:见「Hall」「Room」标签页。" })
|
|
3598
|
+
] }),
|
|
3550
3599
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: { ...S.flexRow, marginBottom: 10 }, children: [
|
|
3551
3600
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: S.muted, children: loading ? "加载中…" : countText }),
|
|
3601
|
+
usageSummary ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: S.muted, title: "老化权重效果口径:参与召回尝试的记忆中,进 topN(被注入上下文)的比例", children: "召回使用 " + usageSummary.used + "/" + usageSummary.attempted + "(" + usageSummary.rate + "%)" }) : null,
|
|
3552
3602
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: S.muted, children: "退场筛查" }),
|
|
3553
3603
|
[["", "全部"], ["active", "仅活跃"], ["retired", "仅退场"]].map(([val, label]) => {
|
|
3554
3604
|
const on = retiredFilter === val;
|
|
@@ -3652,6 +3702,29 @@ var __defProp = Object.defineProperty;
|
|
|
3652
3702
|
}
|
|
3653
3703
|
) : null,
|
|
3654
3704
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: S.muted, children: "优先级 " + m.priority }),
|
|
3705
|
+
m.agingWeight != null ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3706
|
+
"span",
|
|
3707
|
+
{
|
|
3708
|
+
style: {
|
|
3709
|
+
fontSize: 11,
|
|
3710
|
+
padding: "1px 6px",
|
|
3711
|
+
borderRadius: 999,
|
|
3712
|
+
whiteSpace: "nowrap",
|
|
3713
|
+
border: "1px solid " + (m.agingWeight <= 0.6 ? "var(--dsh-mem-danger)" : "var(--dsh-mem-border)"),
|
|
3714
|
+
color: m.agingWeight <= 0.6 ? "var(--dsh-mem-danger)" : "var(--dsh-mem-text-2)"
|
|
3715
|
+
},
|
|
3716
|
+
title: "老化权重 " + Math.round(m.agingWeight * 100) + "%(读路径衰减系数 max(0.5, 0.5^(Δ天/半衰期)));越低越老",
|
|
3717
|
+
children: "老化 " + Math.round(m.agingWeight * 100) + "%"
|
|
3718
|
+
}
|
|
3719
|
+
) : null,
|
|
3720
|
+
m.usage ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3721
|
+
"span",
|
|
3722
|
+
{
|
|
3723
|
+
style: S.muted,
|
|
3724
|
+
title: "召回使用 " + m.usage.used + "/" + m.usage.attempts + "(进 topN 才算 used)" + (m.usage.lastUsedAt ? ";最近使用 " + fmtTime(new Date(m.usage.lastUsedAt).toISOString()) : "") + (m.usage.lastAttemptAt ? ";最近尝试 " + fmtTime(new Date(m.usage.lastAttemptAt).toISOString()) : ""),
|
|
3725
|
+
children: "用 " + m.usage.used + "/" + m.usage.attempts
|
|
3726
|
+
}
|
|
3727
|
+
) : null,
|
|
3655
3728
|
m.score !== null && m.score !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: S.muted, children: "相关度 " + Number(m.score).toFixed(2) }) : null,
|
|
3656
3729
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: S.grow }),
|
|
3657
3730
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: S.muted, children: fmtTime(m.updatedAt) }),
|
|
@@ -3774,14 +3847,936 @@ var __defProp = Object.defineProperty;
|
|
|
3774
3847
|
] });
|
|
3775
3848
|
}
|
|
3776
3849
|
|
|
3777
|
-
// client/src/tabs/
|
|
3850
|
+
// client/src/tabs/RoomsTab.tsx
|
|
3778
3851
|
var import_react16 = require("react");
|
|
3779
3852
|
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
3853
|
+
function majorOf(slug) {
|
|
3854
|
+
const i = slug.indexOf("/");
|
|
3855
|
+
return i === -1 ? slug : slug.slice(0, i);
|
|
3856
|
+
}
|
|
3857
|
+
function suffixOf(slug) {
|
|
3858
|
+
const i = slug.indexOf("/");
|
|
3859
|
+
return i === -1 ? slug : slug.slice(i + 1);
|
|
3860
|
+
}
|
|
3861
|
+
function downloadCsv(csv, name) {
|
|
3862
|
+
const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
|
|
3863
|
+
const url = URL.createObjectURL(blob);
|
|
3864
|
+
const a = document.createElement("a");
|
|
3865
|
+
a.href = url;
|
|
3866
|
+
a.download = name;
|
|
3867
|
+
a.click();
|
|
3868
|
+
URL.revokeObjectURL(url);
|
|
3869
|
+
}
|
|
3870
|
+
var btn = {
|
|
3871
|
+
cursor: "pointer",
|
|
3872
|
+
fontSize: 12,
|
|
3873
|
+
padding: "2px 8px",
|
|
3874
|
+
borderRadius: 6,
|
|
3875
|
+
border: "1px solid var(--dsh-mem-border)",
|
|
3876
|
+
background: "transparent",
|
|
3877
|
+
color: "var(--dsh-mem-text-2)"
|
|
3878
|
+
};
|
|
3879
|
+
function RoomsTab(props) {
|
|
3880
|
+
const rpc = props.rpc;
|
|
3881
|
+
const [registry, setRegistry] = (0, import_react16.useState)([]);
|
|
3882
|
+
const [rooms, setRooms] = (0, import_react16.useState)([]);
|
|
3883
|
+
const [orphanCount, setOrphanCount] = (0, import_react16.useState)(0);
|
|
3884
|
+
const [busy, setBusy] = (0, import_react16.useState)(false);
|
|
3885
|
+
const [msg, setMsg] = (0, import_react16.useState)(null);
|
|
3886
|
+
const [error, setError] = (0, import_react16.useState)(null);
|
|
3887
|
+
const [hiPriv, setHiPriv] = (0, import_react16.useState)(false);
|
|
3888
|
+
const [hiPrivBusy, setHiPrivBusy] = (0, import_react16.useState)(false);
|
|
3889
|
+
const [regSlug, setRegSlug] = (0, import_react16.useState)("");
|
|
3890
|
+
const [regLabel, setRegLabel] = (0, import_react16.useState)("");
|
|
3891
|
+
const [regDesc, setRegDesc] = (0, import_react16.useState)("");
|
|
3892
|
+
const [form, setForm] = (0, import_react16.useState)(null);
|
|
3893
|
+
const [preview, setPreview] = (0, import_react16.useState)(null);
|
|
3894
|
+
const [grownOpen, setGrownOpen] = (0, import_react16.useState)(false);
|
|
3895
|
+
const [dragOverHall, setDragOverHall] = (0, import_react16.useState)(null);
|
|
3896
|
+
const [picking, setPicking] = (0, import_react16.useState)(null);
|
|
3897
|
+
const [editing, setEditing] = (0, import_react16.useState)(null);
|
|
3898
|
+
const [expandedRooms, setExpandedRooms] = (0, import_react16.useState)(/* @__PURE__ */ new Set());
|
|
3899
|
+
const [roomRecords, setRoomRecords] = (0, import_react16.useState)({});
|
|
3900
|
+
const dragRef = (0, import_react16.useRef)(null);
|
|
3901
|
+
const [ghost, setGhost] = (0, import_react16.useState)(null);
|
|
3902
|
+
const onDragLabelDown = (ev, slug) => {
|
|
3903
|
+
if (!hiPriv || busy || ev.button !== 0) return;
|
|
3904
|
+
ev.currentTarget.setPointerCapture(ev.pointerId);
|
|
3905
|
+
dragRef.current = { from: slug, x: ev.clientX, y: ev.clientY, active: false };
|
|
3906
|
+
};
|
|
3907
|
+
const onDragLabelMove = (ev) => {
|
|
3908
|
+
const d = dragRef.current;
|
|
3909
|
+
if (!d) return;
|
|
3910
|
+
if (!d.active && Math.hypot(ev.clientX - d.x, ev.clientY - d.y) > 6) d.active = true;
|
|
3911
|
+
if (!d.active) return;
|
|
3912
|
+
setGhost({ x: ev.clientX, y: ev.clientY, text: d.from });
|
|
3913
|
+
const hall = document.elementFromPoint(ev.clientX, ev.clientY)?.closest?.("[data-hall]")?.getAttribute("data-hall") ?? null;
|
|
3914
|
+
setDragOverHall(hall);
|
|
3915
|
+
};
|
|
3916
|
+
const onDragLabelUp = (ev) => {
|
|
3917
|
+
const d = dragRef.current;
|
|
3918
|
+
dragRef.current = null;
|
|
3919
|
+
setGhost(null);
|
|
3920
|
+
if (!d) return;
|
|
3921
|
+
if (!d.active) return;
|
|
3922
|
+
const hall = document.elementFromPoint(ev.clientX, ev.clientY)?.closest?.("[data-hall]")?.getAttribute("data-hall");
|
|
3923
|
+
setDragOverHall(null);
|
|
3924
|
+
if (hall) beginReclass(d.from, hall);
|
|
3925
|
+
};
|
|
3926
|
+
const loadAll = (0, import_react16.useCallback)(() => {
|
|
3927
|
+
rpc("dsh-memory/rooms-get", {}).then((r) => {
|
|
3928
|
+
if (r && r.ok) {
|
|
3929
|
+
setRooms(r.value.rooms ?? []);
|
|
3930
|
+
setRegistry(r.value.registry ?? []);
|
|
3931
|
+
setOrphanCount(r.value.orphanCount ?? 0);
|
|
3932
|
+
}
|
|
3933
|
+
}).catch(() => void 0);
|
|
3934
|
+
}, [rpc]);
|
|
3935
|
+
const loadHiPriv = (0, import_react16.useCallback)(() => {
|
|
3936
|
+
rpc("dsh-memory/settings-get", {}).then((r) => {
|
|
3937
|
+
if (r && r.ok && r.value) setHiPriv(!!r.value.settings.memoryMutate);
|
|
3938
|
+
}).catch(() => {
|
|
3939
|
+
});
|
|
3940
|
+
}, [rpc]);
|
|
3941
|
+
(0, import_react16.useEffect)(() => {
|
|
3942
|
+
loadAll();
|
|
3943
|
+
loadHiPriv();
|
|
3944
|
+
}, [loadAll, loadHiPriv]);
|
|
3945
|
+
const toggleHiPriv = () => {
|
|
3946
|
+
const next = !hiPriv;
|
|
3947
|
+
if (next && !window.confirm("开启高权限模式:模型获得写入/删除记忆工具,并解锁 Hall 页的分类管理操作。确定开启?")) return;
|
|
3948
|
+
setHiPrivBusy(true);
|
|
3949
|
+
rpc("dsh-memory/settings-set", { memoryMutate: next }).then((r) => {
|
|
3950
|
+
if (r && r.ok) setHiPriv(next);
|
|
3951
|
+
else if (r) setError(r.error ? r.error.message : "切换高权限模式失败");
|
|
3952
|
+
setHiPrivBusy(false);
|
|
3953
|
+
loadHiPriv();
|
|
3954
|
+
}).catch((e) => {
|
|
3955
|
+
setHiPrivBusy(false);
|
|
3956
|
+
setError(String(e && e.message || e));
|
|
3957
|
+
});
|
|
3958
|
+
};
|
|
3959
|
+
const run = (fn) => {
|
|
3960
|
+
if (busy) return;
|
|
3961
|
+
setBusy(true);
|
|
3962
|
+
setError(null);
|
|
3963
|
+
setMsg(null);
|
|
3964
|
+
fn().finally(() => setBusy(false));
|
|
3965
|
+
};
|
|
3966
|
+
const register = () => run(async () => {
|
|
3967
|
+
const r = await rpc("dsh-memory/room-register", {
|
|
3968
|
+
slug: regSlug.trim(),
|
|
3969
|
+
label: regLabel.trim() || void 0,
|
|
3970
|
+
description: regDesc.trim() || void 0
|
|
3971
|
+
});
|
|
3972
|
+
if (r && r.ok) {
|
|
3973
|
+
setMsg(r.value?.notice ?? `已注册:${regSlug.trim()}`);
|
|
3974
|
+
setRegSlug("");
|
|
3975
|
+
setRegLabel("");
|
|
3976
|
+
setRegDesc("");
|
|
3977
|
+
loadAll();
|
|
3978
|
+
} else setError(r && r.error ? r.error.message : "注册失败");
|
|
3979
|
+
});
|
|
3980
|
+
const adopt = (slug) => run(async () => {
|
|
3981
|
+
const r = await rpc("dsh-memory/room-register", { slug, source: "grown" });
|
|
3982
|
+
if (r && r.ok) {
|
|
3983
|
+
setMsg(`已收编:${slug}(source=grown)`);
|
|
3984
|
+
loadAll();
|
|
3985
|
+
} else setError(r && r.error ? r.error.message : "收编失败");
|
|
3986
|
+
});
|
|
3987
|
+
const previewMerge = (override) => run(async () => {
|
|
3988
|
+
const fm = override ?? form;
|
|
3989
|
+
if (!fm) return;
|
|
3990
|
+
const to = fm.to.trim();
|
|
3991
|
+
if (!to) {
|
|
3992
|
+
setError("目标 slug 不能为空");
|
|
3993
|
+
return;
|
|
3994
|
+
}
|
|
3995
|
+
if (to === fm.from) {
|
|
3996
|
+
setError("目标与源相同");
|
|
3997
|
+
return;
|
|
3998
|
+
}
|
|
3999
|
+
const r = await rpc("dsh-memory/room-admin", { action: fm.action, from: fm.from, to });
|
|
4000
|
+
if (r && r.ok) {
|
|
4001
|
+
setPreview({ action: fm.action, from: fm.from, to, notice: String(r.value?.notice ?? "") });
|
|
4002
|
+
} else setError(r && r.error ? r.error.message : "预览失败");
|
|
4003
|
+
});
|
|
4004
|
+
const beginReclass = (from, hall) => {
|
|
4005
|
+
if (!busy) {
|
|
4006
|
+
setMsg(null);
|
|
4007
|
+
setError(null);
|
|
4008
|
+
}
|
|
4009
|
+
if (!hall) {
|
|
4010
|
+
setError("尚无 hall:先注册一个(如 dsh-plugin),再归类。");
|
|
4011
|
+
return;
|
|
4012
|
+
}
|
|
4013
|
+
if (majorOf(from) === hall) {
|
|
4014
|
+
if (registry.some((e) => e.slug === from)) {
|
|
4015
|
+
setMsg(`${from} 已在 ${hall} 下。`);
|
|
4016
|
+
return;
|
|
4017
|
+
}
|
|
4018
|
+
setPicking(null);
|
|
4019
|
+
void adopt(from);
|
|
4020
|
+
return;
|
|
4021
|
+
}
|
|
4022
|
+
const f = { action: "rename", from, to: `${hall}/${suffixOf(from)}` };
|
|
4023
|
+
setForm(f);
|
|
4024
|
+
setPreview(null);
|
|
4025
|
+
void previewMerge(f);
|
|
4026
|
+
};
|
|
4027
|
+
const execMerge = () => run(async () => {
|
|
4028
|
+
if (!preview) return;
|
|
4029
|
+
if (preview.action === "rename" && !registry.some((e) => e.slug === preview.from)) {
|
|
4030
|
+
const rr = await rpc("dsh-memory/room-register", { slug: preview.from, source: "grown" });
|
|
4031
|
+
if (!rr || !rr.ok) {
|
|
4032
|
+
setError(rr && rr.error ? rr.error.message : "收编源失败");
|
|
4033
|
+
return;
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
4036
|
+
const r = await rpc("dsh-memory/room-admin", { action: preview.action, from: preview.from, to: preview.to, dryRun: false });
|
|
4037
|
+
if (r && r.ok) {
|
|
4038
|
+
setMsg(String(r.value?.notice ?? "完成"));
|
|
4039
|
+
setPreview(null);
|
|
4040
|
+
setForm(null);
|
|
4041
|
+
loadAll();
|
|
4042
|
+
} else setError(r && r.error ? r.error.message : "执行失败");
|
|
4043
|
+
});
|
|
4044
|
+
const retire = (slug, active, withRecords = false) => run(async () => {
|
|
4045
|
+
if (!active) {
|
|
4046
|
+
const tip = withRecords ? `退役 Room:${slug} 并退场其下全部记忆?(记录为软删,可在记忆页「已退场」区逐条恢复)` : `退役 Room:${slug}?(存量记录的 tags 不动,仅词表不再推荐;可随时恢复)`;
|
|
4047
|
+
if (!window.confirm(tip)) return;
|
|
4048
|
+
}
|
|
4049
|
+
const r = await rpc("dsh-memory/room-admin", { action: "retire", slug, active, withRecords: withRecords || void 0 });
|
|
4050
|
+
if (r && r.ok) {
|
|
4051
|
+
setMsg(String(r.value?.notice ?? "完成"));
|
|
4052
|
+
loadAll();
|
|
4053
|
+
} else setError(r && r.error ? r.error.message : "操作失败");
|
|
4054
|
+
});
|
|
4055
|
+
const saveEditing = () => run(async () => {
|
|
4056
|
+
if (!editing) return;
|
|
4057
|
+
const r = await rpc("dsh-memory/room-admin", { action: "update", slug: editing.slug, label: editing.label, description: editing.description });
|
|
4058
|
+
if (r && r.ok) {
|
|
4059
|
+
setMsg(String(r.value?.notice ?? "已更新"));
|
|
4060
|
+
setEditing(null);
|
|
4061
|
+
loadAll();
|
|
4062
|
+
} else setError(r && r.error ? r.error.message : "更新失败");
|
|
4063
|
+
});
|
|
4064
|
+
const toggleRoomExpand = (slug) => run(async () => {
|
|
4065
|
+
if (expandedRooms.has(slug)) {
|
|
4066
|
+
const next = new Set(expandedRooms);
|
|
4067
|
+
next.delete(slug);
|
|
4068
|
+
setExpandedRooms(next);
|
|
4069
|
+
return;
|
|
4070
|
+
}
|
|
4071
|
+
const r = await rpc("dsh-memory/list-records", { tag: slug, limit: 10 });
|
|
4072
|
+
if (r && r.ok) {
|
|
4073
|
+
const next = new Set(expandedRooms);
|
|
4074
|
+
next.add(slug);
|
|
4075
|
+
setExpandedRooms(next);
|
|
4076
|
+
setRoomRecords((m) => ({ ...m, [slug]: { items: r.value.items, total: r.value.total ?? null } }));
|
|
4077
|
+
} else setError(r && r.error ? r.error.message : "加载失败");
|
|
4078
|
+
});
|
|
4079
|
+
const exportRecords = (slug) => run(async () => {
|
|
4080
|
+
const r = await rpc("dsh-memory/rooms-export", { kind: "records", tag: slug });
|
|
4081
|
+
if (!r.ok) {
|
|
4082
|
+
setError(r.error.message);
|
|
4083
|
+
return;
|
|
4084
|
+
}
|
|
4085
|
+
if (r.value.csv) downloadCsv(r.value.csv, `dsh-memory-room-${slug.replace(/\//g, "-")}-records.csv`);
|
|
4086
|
+
else setError("导出为空");
|
|
4087
|
+
});
|
|
4088
|
+
const groups = (0, import_react16.useMemo)(() => {
|
|
4089
|
+
const countOf = new Map(rooms.map((r) => [r.room, r.count]));
|
|
4090
|
+
const byHall = /* @__PURE__ */ new Map();
|
|
4091
|
+
for (const e of registry) {
|
|
4092
|
+
const hall = majorOf(e.slug);
|
|
4093
|
+
const list = byHall.get(hall);
|
|
4094
|
+
if (list) list.push(e);
|
|
4095
|
+
else byHall.set(hall, [e]);
|
|
4096
|
+
}
|
|
4097
|
+
return [...byHall.entries()].map(([hall, entries]) => ({
|
|
4098
|
+
hall,
|
|
4099
|
+
label: registry.find((e) => e.slug === hall)?.label,
|
|
4100
|
+
entries: [...entries].sort((a, b) => a.slug.localeCompare(b.slug)),
|
|
4101
|
+
total: entries.reduce((s2, e) => s2 + (countOf.get(e.slug) ?? 0), 0)
|
|
4102
|
+
})).sort((a, b) => a.hall.localeCompare(b.hall));
|
|
4103
|
+
}, [registry, rooms]);
|
|
4104
|
+
const grownOnly = (0, import_react16.useMemo)(() => rooms.filter((r) => !registry.some((e) => e.slug === r.room)), [rooms, registry]);
|
|
4105
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { children: [
|
|
4106
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { ...S.flexRow, marginBottom: 8 }, children: [
|
|
4107
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: "Hall = Room 大类 · 大类上限 200,小类「大类/小类」细分不占额度 · 与 Wing(域)筛选无关" }),
|
|
4108
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: S.grow }),
|
|
4109
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4110
|
+
"button",
|
|
4111
|
+
{
|
|
4112
|
+
type: "button",
|
|
4113
|
+
title: "导出分类数据 CSV(Room 词表+计数)",
|
|
4114
|
+
onClick: () => run(async () => {
|
|
4115
|
+
const r = await rpc("dsh-memory/rooms-export", { kind: "rooms" });
|
|
4116
|
+
if (!r.ok) {
|
|
4117
|
+
setError(r.error.message);
|
|
4118
|
+
return;
|
|
4119
|
+
}
|
|
4120
|
+
if (r.value.csv) downloadCsv(r.value.csv, "dsh-memory-rooms.csv");
|
|
4121
|
+
}),
|
|
4122
|
+
style: btn,
|
|
4123
|
+
children: "导出词表"
|
|
4124
|
+
}
|
|
4125
|
+
),
|
|
4126
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
4127
|
+
"button",
|
|
4128
|
+
{
|
|
4129
|
+
type: "button",
|
|
4130
|
+
title: "导出无 Room 绑定的孤儿清单(含候选)",
|
|
4131
|
+
onClick: () => run(async () => {
|
|
4132
|
+
const r = await rpc("dsh-memory/rooms-export", { kind: "orphans" });
|
|
4133
|
+
if (!r.ok) {
|
|
4134
|
+
setError(r.error.message);
|
|
4135
|
+
return;
|
|
4136
|
+
}
|
|
4137
|
+
if (r.value.csv) downloadCsv(r.value.csv, "dsh-memory-room-orphans.csv");
|
|
4138
|
+
}),
|
|
4139
|
+
style: btn,
|
|
4140
|
+
children: [
|
|
4141
|
+
"导出孤儿 · ",
|
|
4142
|
+
orphanCount
|
|
4143
|
+
]
|
|
4144
|
+
}
|
|
4145
|
+
),
|
|
4146
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4147
|
+
"button",
|
|
4148
|
+
{
|
|
4149
|
+
type: "button",
|
|
4150
|
+
style: {
|
|
4151
|
+
...btn,
|
|
4152
|
+
color: hiPriv ? "var(--dsh-mem-danger)" : void 0,
|
|
4153
|
+
border: hiPriv ? "1px solid var(--dsh-mem-danger)" : btn.border
|
|
4154
|
+
},
|
|
4155
|
+
disabled: hiPrivBusy,
|
|
4156
|
+
title: hiPriv ? "关闭高权限模式(收回模型写删工具与管理操作)" : "开启高权限模式以解锁分类管理操作",
|
|
4157
|
+
onClick: toggleHiPriv,
|
|
4158
|
+
children: hiPriv ? "高权限:开" : "高权限:关"
|
|
4159
|
+
}
|
|
4160
|
+
),
|
|
4161
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", style: btn, onClick: loadAll, title: "重拉注册表与计数", children: "刷新" })
|
|
4162
|
+
] }),
|
|
4163
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, alignItems: "center", marginBottom: 10 }, children: [
|
|
4164
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4165
|
+
"input",
|
|
4166
|
+
{
|
|
4167
|
+
value: regSlug,
|
|
4168
|
+
onChange: (e) => setRegSlug(e.target.value),
|
|
4169
|
+
placeholder: "slug(如 dsh-plugin 或 dsh-plugin/merge)",
|
|
4170
|
+
disabled: !hiPriv || busy,
|
|
4171
|
+
style: { ...S.input, width: 210, fontSize: 12 }
|
|
4172
|
+
}
|
|
4173
|
+
),
|
|
4174
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4175
|
+
"input",
|
|
4176
|
+
{
|
|
4177
|
+
value: regLabel,
|
|
4178
|
+
onChange: (e) => setRegLabel(e.target.value),
|
|
4179
|
+
placeholder: "名称(可中文)",
|
|
4180
|
+
disabled: !hiPriv || busy,
|
|
4181
|
+
style: { ...S.input, width: 110, fontSize: 12 }
|
|
4182
|
+
}
|
|
4183
|
+
),
|
|
4184
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4185
|
+
"input",
|
|
4186
|
+
{
|
|
4187
|
+
value: regDesc,
|
|
4188
|
+
onChange: (e) => setRegDesc(e.target.value),
|
|
4189
|
+
placeholder: "归类说明(喂标注器)",
|
|
4190
|
+
disabled: !hiPriv || busy,
|
|
4191
|
+
style: { ...S.input, width: 170, fontSize: 12 }
|
|
4192
|
+
}
|
|
4193
|
+
),
|
|
4194
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4195
|
+
"button",
|
|
4196
|
+
{
|
|
4197
|
+
type: "button",
|
|
4198
|
+
disabled: !hiPriv || busy || !regSlug.trim(),
|
|
4199
|
+
onClick: register,
|
|
4200
|
+
style: { ...btn, color: "var(--dsh-mem-accent)", borderColor: "var(--dsh-mem-accent)", opacity: !hiPriv || busy || !regSlug.trim() ? 0.5 : 1 },
|
|
4201
|
+
children: "注册"
|
|
4202
|
+
}
|
|
4203
|
+
),
|
|
4204
|
+
!hiPriv && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: "管理操作需先开启高权限模式" })
|
|
4205
|
+
] }),
|
|
4206
|
+
editing ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { position: "sticky", top: 0, zIndex: 5, background: "var(--dsh-mem-bg-inset)", display: "flex", alignItems: "center", gap: 6, marginBottom: 8, flexWrap: "wrap", padding: "6px 8px", border: "1px solid var(--dsh-mem-accent)", borderRadius: 8 }, children: [
|
|
4207
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { style: S.muted, children: [
|
|
4208
|
+
"编辑 ",
|
|
4209
|
+
editing.slug
|
|
4210
|
+
] }),
|
|
4211
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("input", { value: editing.label, onChange: (ev) => setEditing({ ...editing, label: ev.target.value }), placeholder: "显示名(可中文)", disabled: busy, style: { ...S.input, width: 140, fontSize: 12 } }),
|
|
4212
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("input", { value: editing.description, onChange: (ev) => setEditing({ ...editing, description: ev.target.value }), placeholder: "归类说明(喂标注器)", disabled: busy, style: { ...S.input, width: 200, fontSize: 12 } }),
|
|
4213
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", disabled: busy, onClick: saveEditing, style: { ...btn, color: "var(--dsh-mem-accent)", borderColor: "var(--dsh-mem-accent)" }, children: "保存" }),
|
|
4214
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", disabled: busy, onClick: () => setEditing(null), style: btn, children: "取消" })
|
|
4215
|
+
] }) : null,
|
|
4216
|
+
form ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { position: "sticky", top: 0, zIndex: 5, background: "var(--dsh-mem-bg-inset)", display: "flex", alignItems: "center", gap: 6, marginBottom: 8, flexWrap: "wrap", padding: "6px 8px", border: "1px solid var(--dsh-mem-accent)", borderRadius: 8 }, children: [
|
|
4217
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { style: S.muted, children: [
|
|
4218
|
+
form.action === "merge" ? "合并" : "归类/改名",
|
|
4219
|
+
" ",
|
|
4220
|
+
form.from,
|
|
4221
|
+
" →"
|
|
4222
|
+
] }),
|
|
4223
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4224
|
+
"input",
|
|
4225
|
+
{
|
|
4226
|
+
value: form.to,
|
|
4227
|
+
onChange: (ev) => setForm({ ...form, to: ev.target.value }),
|
|
4228
|
+
placeholder: "目标 slug",
|
|
4229
|
+
disabled: busy,
|
|
4230
|
+
style: { ...S.input, width: 190, fontSize: 12 }
|
|
4231
|
+
}
|
|
4232
|
+
),
|
|
4233
|
+
form.action === "rename" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { display: "inline-flex", gap: 4, flexWrap: "wrap" }, children: groups.filter((gg) => gg.hall !== majorOf(form.from)).map((gg) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4234
|
+
"button",
|
|
4235
|
+
{
|
|
4236
|
+
type: "button",
|
|
4237
|
+
title: `归入 ${gg.hall} 下(${gg.hall}/${suffixOf(form.from)})`,
|
|
4238
|
+
onClick: () => setForm({ ...form, to: `${gg.hall}/${suffixOf(form.from)}` }),
|
|
4239
|
+
style: { ...btn, borderRadius: 999, padding: "1px 8px" },
|
|
4240
|
+
children: gg.hall
|
|
4241
|
+
},
|
|
4242
|
+
gg.hall
|
|
4243
|
+
)) }) : null,
|
|
4244
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", disabled: busy, onClick: () => previewMerge(), style: { ...btn, color: "var(--dsh-mem-accent)", borderColor: "var(--dsh-mem-accent)" }, children: "预览" }),
|
|
4245
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", disabled: busy, onClick: () => {
|
|
4246
|
+
setForm(null);
|
|
4247
|
+
setPreview(null);
|
|
4248
|
+
setPicking(null);
|
|
4249
|
+
}, style: btn, children: "取消" }),
|
|
4250
|
+
picking ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: "挑选中:点击目标 hall 头完成归类" }) : null,
|
|
4251
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: "与反刍并发会互相覆盖,请在反刍空闲时执行。" })
|
|
4252
|
+
] }) : null,
|
|
4253
|
+
preview ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { position: "sticky", top: 0, zIndex: 5, background: "var(--dsh-mem-bg-inset)", marginBottom: 8, padding: "6px 8px", border: "1px solid var(--dsh-mem-danger)", borderRadius: 8, fontSize: 12 }, children: [
|
|
4254
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { children: preview.notice }),
|
|
4255
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { display: "flex", gap: 6, marginTop: 4 }, children: [
|
|
4256
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { type: "button", disabled: busy, onClick: execMerge, style: { ...btn, color: "var(--dsh-mem-danger)", borderColor: "var(--dsh-mem-danger)" }, children: [
|
|
4257
|
+
"确认执行",
|
|
4258
|
+
preview.action === "merge" ? "合并" : "归类/改名"
|
|
4259
|
+
] }),
|
|
4260
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", disabled: busy, onClick: () => setPreview(null), style: btn, children: "再改改" })
|
|
4261
|
+
] })
|
|
4262
|
+
] }) : null,
|
|
4263
|
+
msg ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: S.hint, children: msg }) : null,
|
|
4264
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: S.error, children: error }) : null,
|
|
4265
|
+
ghost ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { position: "fixed", left: ghost.x + 12, top: ghost.y + 12, zIndex: 9999, pointerEvents: "none", fontSize: 12, padding: "2px 10px", borderRadius: 999, border: "1px solid var(--dsh-mem-accent)", background: "var(--dsh-mem-bg-inset)", color: "var(--dsh-mem-accent)" }, children: ghost.text }) : null,
|
|
4266
|
+
groups.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: S.intro, children: "注册表为空。在上方注册 Hall/Room(如 dsh-plugin),或把自生长 slug「收编」进词表 —— 之后候选标注器会优先把相关记忆挂到这些 slug 上。" }) : groups.map((g) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { marginBottom: 10, border: "1px solid var(--dsh-mem-border)", borderRadius: 8, overflow: "hidden" }, children: [
|
|
4267
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
4268
|
+
"div",
|
|
4269
|
+
{
|
|
4270
|
+
"data-hall": g.hall,
|
|
4271
|
+
onDragOver: (ev) => {
|
|
4272
|
+
if (!hiPriv || busy) return;
|
|
4273
|
+
ev.preventDefault();
|
|
4274
|
+
ev.dataTransfer.dropEffect = "move";
|
|
4275
|
+
setDragOverHall(g.hall);
|
|
4276
|
+
},
|
|
4277
|
+
onDragLeave: () => setDragOverHall((cur) => cur === g.hall ? null : cur),
|
|
4278
|
+
onDrop: (ev) => {
|
|
4279
|
+
ev.preventDefault();
|
|
4280
|
+
setDragOverHall(null);
|
|
4281
|
+
if (!hiPriv || busy) return;
|
|
4282
|
+
const from = ev.dataTransfer.getData("text/dsh-room");
|
|
4283
|
+
if (from) beginReclass(from, g.hall);
|
|
4284
|
+
},
|
|
4285
|
+
onClick: () => {
|
|
4286
|
+
if (!picking || busy) return;
|
|
4287
|
+
const from = picking;
|
|
4288
|
+
setPicking(null);
|
|
4289
|
+
beginReclass(from, g.hall);
|
|
4290
|
+
},
|
|
4291
|
+
style: {
|
|
4292
|
+
display: "flex",
|
|
4293
|
+
alignItems: "center",
|
|
4294
|
+
gap: 8,
|
|
4295
|
+
padding: "5px 10px",
|
|
4296
|
+
background: "var(--dsh-mem-bg-inset)",
|
|
4297
|
+
outline: dragOverHall === g.hall || picking !== null ? "2px dashed var(--dsh-mem-accent)" : void 0,
|
|
4298
|
+
outlineOffset: dragOverHall === g.hall || picking !== null ? "-2px" : void 0,
|
|
4299
|
+
cursor: picking ? "pointer" : void 0
|
|
4300
|
+
},
|
|
4301
|
+
title: picking ? "点击此 hall:把「" + picking + "」归入 " + g.hall + " 下(major/minor,预览后确认)" : hiPriv ? "拖入 room 归类到该 hall 下(major/minor;记录随迁,旧名进别名,预览后确认)" : void 0,
|
|
4302
|
+
children: [
|
|
4303
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { style: { fontSize: 12, fontWeight: 600 }, children: [
|
|
4304
|
+
g.hall,
|
|
4305
|
+
g.label ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: " (" + g.label + ")" }) : null
|
|
4306
|
+
] }),
|
|
4307
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: `合计 ${g.total} 条 · ${g.entries.length} 个 room` }),
|
|
4308
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: S.grow }),
|
|
4309
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4310
|
+
"button",
|
|
4311
|
+
{
|
|
4312
|
+
type: "button",
|
|
4313
|
+
disabled: !hiPriv || busy,
|
|
4314
|
+
title: `预填注册表单为 ${g.hall}/…(注册该 hall 下的小类)`,
|
|
4315
|
+
onClick: () => setRegSlug(g.hall + "/"),
|
|
4316
|
+
style: btn,
|
|
4317
|
+
children: "+小类"
|
|
4318
|
+
}
|
|
4319
|
+
)
|
|
4320
|
+
]
|
|
4321
|
+
}
|
|
4322
|
+
),
|
|
4323
|
+
g.entries.map((e) => {
|
|
4324
|
+
const standalone = e.slug === g.hall;
|
|
4325
|
+
const count = rooms.find((r) => r.room === e.slug)?.count ?? 0;
|
|
4326
|
+
const retired = e.status === "retired";
|
|
4327
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { padding: "4px 10px", borderTop: "1px solid var(--dsh-mem-border)", opacity: retired ? 0.55 : 1 }, children: [
|
|
4328
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
|
|
4329
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { flex: 1, minWidth: 0, display: "flex", flexWrap: "wrap", gap: "2px 8px", alignItems: "baseline" }, children: [
|
|
4330
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4331
|
+
"button",
|
|
4332
|
+
{
|
|
4333
|
+
type: "button",
|
|
4334
|
+
title: expandedRooms.has(e.slug) ? "收起内容" : "展开查看该 Room 的记录内容(默认收起)",
|
|
4335
|
+
onClick: () => void toggleRoomExpand(e.slug),
|
|
4336
|
+
style: { ...btn, borderRadius: 999, padding: "1px 7px", flexShrink: 0 },
|
|
4337
|
+
children: expandedRooms.has(e.slug) ? "▾" : "▸"
|
|
4338
|
+
}
|
|
4339
|
+
),
|
|
4340
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
4341
|
+
"span",
|
|
4342
|
+
{
|
|
4343
|
+
style: { fontSize: 12, cursor: hiPriv && !busy ? "grab" : "default", touchAction: "none" },
|
|
4344
|
+
onPointerDown: (ev) => onDragLabelDown(ev, e.slug),
|
|
4345
|
+
onPointerMove: onDragLabelMove,
|
|
4346
|
+
onPointerUp: onDragLabelUp,
|
|
4347
|
+
title: hiPriv && !busy ? "按住拖到某个 hall 头上归类(major/minor;记录随迁,旧名进别名)" : e.description ? `${e.slug} — ${e.description}` : e.slug,
|
|
4348
|
+
children: [
|
|
4349
|
+
standalone ? e.slug : "· " + e.slug.slice(g.hall.length + 1),
|
|
4350
|
+
e.source === "pre-registered" ? " ⭐" : ""
|
|
4351
|
+
]
|
|
4352
|
+
}
|
|
4353
|
+
),
|
|
4354
|
+
e.label ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: e.label }) : null,
|
|
4355
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: count + " 条" }),
|
|
4356
|
+
retired ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { ...S.muted, color: "var(--dsh-mem-danger)" }, children: "已退役" }) : null,
|
|
4357
|
+
(e.aliases ?? []).length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { ...S.muted, wordBreak: "break-all" }, children: "别名: " + (e.aliases ?? []).join(", ") }) : null
|
|
4358
|
+
] }),
|
|
4359
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { display: "flex", gap: 6, flexShrink: 0, alignItems: "center" }, children: [
|
|
4360
|
+
!hiPriv ? null : /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
4361
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", disabled: busy, title: "编辑显示名/归类说明", onClick: () => {
|
|
4362
|
+
setEditing({ slug: e.slug, label: e.label ?? "", description: e.description ?? "" });
|
|
4363
|
+
setForm(null);
|
|
4364
|
+
setPreview(null);
|
|
4365
|
+
setPicking(null);
|
|
4366
|
+
}, style: btn, children: "编辑" }),
|
|
4367
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4368
|
+
"button",
|
|
4369
|
+
{
|
|
4370
|
+
type: "button",
|
|
4371
|
+
disabled: busy,
|
|
4372
|
+
title: picking === e.slug ? "再点一次取消挑选;然后点击目标 hall 头完成归类" : "归类到已有 hall 下:点击后选目标 hall(记录随迁,旧名进别名)",
|
|
4373
|
+
onClick: () => {
|
|
4374
|
+
if (picking === e.slug) {
|
|
4375
|
+
setPicking(null);
|
|
4376
|
+
return;
|
|
4377
|
+
}
|
|
4378
|
+
setForm({ action: "rename", from: e.slug, to: "" });
|
|
4379
|
+
setPreview(null);
|
|
4380
|
+
setPicking(e.slug);
|
|
4381
|
+
setMsg(null);
|
|
4382
|
+
setError(null);
|
|
4383
|
+
},
|
|
4384
|
+
style: { ...btn, ...picking === e.slug ? { color: "var(--dsh-mem-accent)", borderColor: "var(--dsh-mem-accent)" } : {} },
|
|
4385
|
+
children: picking === e.slug ? "选 hall…" : "归类"
|
|
4386
|
+
}
|
|
4387
|
+
),
|
|
4388
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", disabled: busy, title: `改名(= merge 1:1 + 注册表改名)`, onClick: () => {
|
|
4389
|
+
setForm({ action: "rename", from: e.slug, to: "" });
|
|
4390
|
+
setPreview(null);
|
|
4391
|
+
}, style: btn, children: "改名" }),
|
|
4392
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", disabled: busy, title: "合并到另一个 Room(dryRun 预览→确认实跑)", onClick: () => {
|
|
4393
|
+
setForm({ action: "merge", from: e.slug, to: "" });
|
|
4394
|
+
setPreview(null);
|
|
4395
|
+
}, style: btn, children: "合并" }),
|
|
4396
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", disabled: busy, title: retired ? "恢复该 Room 到词表" : "退役该 Room(存量 tags 不动)", onClick: () => retire(e.slug, retired), style: { ...btn, ...retired ? { color: "var(--dsh-mem-accent)", borderColor: "var(--dsh-mem-accent)" } : { color: "var(--dsh-mem-danger)", borderColor: "var(--dsh-mem-danger)" } }, children: retired ? "恢复" : "退役" }),
|
|
4397
|
+
!retired ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", disabled: busy, title: "退役并退场该 Room 下全部记忆(软删,可在「已退场」区恢复)", onClick: () => retire(e.slug, false, true), style: { ...btn, color: "var(--dsh-mem-danger)", borderColor: "var(--dsh-mem-danger)" }, children: "退场" }) : null
|
|
4398
|
+
] }),
|
|
4399
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", disabled: busy, title: "导出该 Room 的记录清单 CSV", onClick: () => exportRecords(e.slug), style: btn, children: "导出" })
|
|
4400
|
+
] })
|
|
4401
|
+
] }),
|
|
4402
|
+
expandedRooms.has(e.slug) ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { margin: "4px 0 2px", padding: "4px 8px", border: "1px dashed var(--dsh-mem-border)", borderRadius: 6 }, children: [
|
|
4403
|
+
(roomRecords[e.slug]?.items ?? []).map((it) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { fontSize: 11, padding: "3px 0", borderBottom: "1px solid var(--dsh-mem-border)" }, children: [
|
|
4404
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "dsh-mem-tag dsh-mem-tag-" + it.type, children: TYPE_LABELS[it.type] || it.type }),
|
|
4405
|
+
" ",
|
|
4406
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: it.updatedAt ? fmtTime(it.updatedAt) : "" }),
|
|
4407
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { marginTop: 2, wordBreak: "break-all", color: "var(--dsh-mem-text-2)" }, children: it.content.length > 120 ? it.content.slice(0, 120) + "…" : it.content })
|
|
4408
|
+
] }, it.id)),
|
|
4409
|
+
(roomRecords[e.slug]?.items.length ?? 0) === 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: S.muted, children: "该 Room 暂无记录。" }) : null,
|
|
4410
|
+
(roomRecords[e.slug]?.total ?? 0) > (roomRecords[e.slug]?.items.length ?? 0) ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { ...S.muted, fontSize: 11 }, children: "共 " + roomRecords[e.slug].total + " 条,仅显示前 " + roomRecords[e.slug].items.length + " 条" }) : null
|
|
4411
|
+
] }) : null
|
|
4412
|
+
] }, e.slug);
|
|
4413
|
+
})
|
|
4414
|
+
] }, g.hall)),
|
|
4415
|
+
grownOnly.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { marginTop: 4 }, children: [
|
|
4416
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { type: "button", onClick: () => setGrownOpen(!grownOpen), style: btn, title: grownOpen ? "收起未注册自生长 Room" : "展开未注册自生长 Room", children: [
|
|
4417
|
+
grownOpen ? "▾" : "▸",
|
|
4418
|
+
" 自生长未注册 · 共 ",
|
|
4419
|
+
grownOnly.length
|
|
4420
|
+
] }),
|
|
4421
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: "(只在 tags 里涌现,不在词表 —— 收编后标注器才会优先挂靠)" }),
|
|
4422
|
+
grownOpen ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, alignItems: "center", marginTop: 6 }, children: grownOnly.map((r) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { style: { display: "inline-flex", alignItems: "center", gap: 4 }, children: [
|
|
4423
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4424
|
+
"span",
|
|
4425
|
+
{
|
|
4426
|
+
style: { ...btn, cursor: hiPriv && !busy ? "grab" : "default", borderRadius: 999, padding: "2px 8px", touchAction: "none" },
|
|
4427
|
+
onPointerDown: (ev) => onDragLabelDown(ev, r.room),
|
|
4428
|
+
onPointerMove: onDragLabelMove,
|
|
4429
|
+
onPointerUp: onDragLabelUp,
|
|
4430
|
+
title: hiPriv && !busy ? "自生长 slug(未注册)——按住拖到某个 hall 头上 = 收编并归为其子类" : "自生长 slug(未注册)",
|
|
4431
|
+
children: r.room + " · " + r.count
|
|
4432
|
+
}
|
|
4433
|
+
),
|
|
4434
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", disabled: !hiPriv || busy, title: "收编进注册表(source=grown,原地)", onClick: () => adopt(r.room), style: { ...btn, color: "var(--dsh-mem-accent)", borderColor: "var(--dsh-mem-accent)", opacity: !hiPriv || busy ? 0.5 : 1 }, children: "收编" }),
|
|
4435
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4436
|
+
"button",
|
|
4437
|
+
{
|
|
4438
|
+
type: "button",
|
|
4439
|
+
disabled: !hiPriv || busy,
|
|
4440
|
+
title: "归类到已有 hall 下:点击后选目标 hall(收编+子类一步,记录随迁)",
|
|
4441
|
+
onClick: () => {
|
|
4442
|
+
if (picking === r.room) {
|
|
4443
|
+
setPicking(null);
|
|
4444
|
+
return;
|
|
4445
|
+
}
|
|
4446
|
+
setForm({ action: "rename", from: r.room, to: "" });
|
|
4447
|
+
setPreview(null);
|
|
4448
|
+
setPicking(r.room);
|
|
4449
|
+
setMsg(null);
|
|
4450
|
+
setError(null);
|
|
4451
|
+
},
|
|
4452
|
+
style: { ...btn, opacity: !hiPriv || busy ? 0.5 : 1, ...picking === r.room ? { color: "var(--dsh-mem-accent)", borderColor: "var(--dsh-mem-accent)" } : {} },
|
|
4453
|
+
children: picking === r.room ? "选 hall…" : "归"
|
|
4454
|
+
}
|
|
4455
|
+
)
|
|
4456
|
+
] }, r.room)) }) : null
|
|
4457
|
+
] }) : null
|
|
4458
|
+
] });
|
|
4459
|
+
}
|
|
4460
|
+
|
|
4461
|
+
// client/src/tabs/RoomTab.tsx
|
|
4462
|
+
var import_react17 = require("react");
|
|
4463
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
4464
|
+
var btn2 = {
|
|
4465
|
+
cursor: "pointer",
|
|
4466
|
+
fontSize: 12,
|
|
4467
|
+
padding: "2px 8px",
|
|
4468
|
+
borderRadius: 6,
|
|
4469
|
+
border: "1px solid var(--dsh-mem-border)",
|
|
4470
|
+
background: "transparent",
|
|
4471
|
+
color: "var(--dsh-mem-text-2)"
|
|
4472
|
+
};
|
|
4473
|
+
function downloadCsv2(csv, name) {
|
|
4474
|
+
const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
|
|
4475
|
+
const url = URL.createObjectURL(blob);
|
|
4476
|
+
const a = document.createElement("a");
|
|
4477
|
+
a.href = url;
|
|
4478
|
+
a.download = name;
|
|
4479
|
+
a.click();
|
|
4480
|
+
URL.revokeObjectURL(url);
|
|
4481
|
+
}
|
|
4482
|
+
function RoomTab(props) {
|
|
4483
|
+
const rpc = props.rpc;
|
|
4484
|
+
const [registry, setRegistry] = (0, import_react17.useState)([]);
|
|
4485
|
+
const [counts, setCounts] = (0, import_react17.useState)([]);
|
|
4486
|
+
const [orphanCount, setOrphanCount] = (0, import_react17.useState)(0);
|
|
4487
|
+
const [busy, setBusy] = (0, import_react17.useState)(false);
|
|
4488
|
+
const [msg, setMsg] = (0, import_react17.useState)(null);
|
|
4489
|
+
const [error, setError] = (0, import_react17.useState)(null);
|
|
4490
|
+
const [hiPriv, setHiPriv] = (0, import_react17.useState)(false);
|
|
4491
|
+
const [hiPrivBusy, setHiPrivBusy] = (0, import_react17.useState)(false);
|
|
4492
|
+
const [reg, setReg] = (0, import_react17.useState)({ slug: "", label: "", description: "" });
|
|
4493
|
+
const [editing, setEditing] = (0, import_react17.useState)(null);
|
|
4494
|
+
const [renaming, setRenaming] = (0, import_react17.useState)(null);
|
|
4495
|
+
const [expanded, setExpanded] = (0, import_react17.useState)(/* @__PURE__ */ new Set());
|
|
4496
|
+
const [records, setRecords] = (0, import_react17.useState)({});
|
|
4497
|
+
const loadAll = (0, import_react17.useCallback)(() => {
|
|
4498
|
+
rpc("dsh-memory/rooms-get", {}).then((r) => {
|
|
4499
|
+
if (r && r.ok) {
|
|
4500
|
+
setCounts(r.value.rooms ?? []);
|
|
4501
|
+
setRegistry(r.value.registry ?? []);
|
|
4502
|
+
setOrphanCount(r.value.orphanCount ?? 0);
|
|
4503
|
+
}
|
|
4504
|
+
}).catch(() => void 0);
|
|
4505
|
+
}, [rpc]);
|
|
4506
|
+
const loadHiPriv = (0, import_react17.useCallback)(() => {
|
|
4507
|
+
rpc("dsh-memory/settings-get", {}).then((r) => {
|
|
4508
|
+
if (r && r.ok && r.value) setHiPriv(!!r.value.settings.memoryMutate);
|
|
4509
|
+
}).catch(() => {
|
|
4510
|
+
});
|
|
4511
|
+
}, [rpc]);
|
|
4512
|
+
(0, import_react17.useEffect)(() => {
|
|
4513
|
+
loadAll();
|
|
4514
|
+
loadHiPriv();
|
|
4515
|
+
}, [loadAll, loadHiPriv]);
|
|
4516
|
+
const toggleHiPriv = () => {
|
|
4517
|
+
const next = !hiPriv;
|
|
4518
|
+
if (next && !window.confirm("开启高权限模式:模型获得写入/删除记忆工具,并解锁 Room 管理操作。确定开启?")) return;
|
|
4519
|
+
rpc("dsh-memory/settings-set", { memoryMutate: next }).then((r) => {
|
|
4520
|
+
if (r && r.ok) setHiPriv(next);
|
|
4521
|
+
setHiPrivBusy(false);
|
|
4522
|
+
loadHiPriv();
|
|
4523
|
+
}).catch(() => setHiPrivBusy(false));
|
|
4524
|
+
};
|
|
4525
|
+
const run = (fn) => {
|
|
4526
|
+
if (busy) return;
|
|
4527
|
+
setBusy(true);
|
|
4528
|
+
setError(null);
|
|
4529
|
+
setMsg(null);
|
|
4530
|
+
fn().finally(() => setBusy(false));
|
|
4531
|
+
};
|
|
4532
|
+
const register = () => run(async () => {
|
|
4533
|
+
const r = await rpc("dsh-memory/room-register", { slug: reg.slug.trim(), label: reg.label.trim() || void 0, description: reg.description.trim() || void 0 });
|
|
4534
|
+
if (r && r.ok) {
|
|
4535
|
+
setMsg(r.value?.notice ?? `已注册:${reg.slug.trim()}`);
|
|
4536
|
+
setReg({ slug: "", label: "", description: "" });
|
|
4537
|
+
loadAll();
|
|
4538
|
+
} else setError(r && r.error ? r.error.message : "注册失败");
|
|
4539
|
+
});
|
|
4540
|
+
const adopt = (slug) => run(async () => {
|
|
4541
|
+
const r = await rpc("dsh-memory/room-register", { slug, source: "grown" });
|
|
4542
|
+
if (r && r.ok) {
|
|
4543
|
+
setMsg(`已收编:${slug}(source=grown)`);
|
|
4544
|
+
loadAll();
|
|
4545
|
+
} else setError(r && r.error ? r.error.message : "收编失败");
|
|
4546
|
+
});
|
|
4547
|
+
const previewRename = () => run(async () => {
|
|
4548
|
+
if (!renaming) return;
|
|
4549
|
+
const to = renaming.to.trim();
|
|
4550
|
+
if (!to) {
|
|
4551
|
+
setError("目标 slug 不能为空");
|
|
4552
|
+
return;
|
|
4553
|
+
}
|
|
4554
|
+
if (to === renaming.slug) {
|
|
4555
|
+
setError("目标与源相同");
|
|
4556
|
+
return;
|
|
4557
|
+
}
|
|
4558
|
+
if (!registry.some((e) => e.slug === renaming.slug)) {
|
|
4559
|
+
const rr = await rpc("dsh-memory/room-register", { slug: renaming.slug, source: "grown" });
|
|
4560
|
+
if (!rr || !rr.ok) {
|
|
4561
|
+
setError(rr && rr.error ? rr.error.message : "收编源失败");
|
|
4562
|
+
return;
|
|
4563
|
+
}
|
|
4564
|
+
loadAll();
|
|
4565
|
+
}
|
|
4566
|
+
const r = await rpc("dsh-memory/room-admin", { action: "rename", from: renaming.slug, to });
|
|
4567
|
+
if (r && r.ok) setMsg(String(r.value?.notice ?? ""));
|
|
4568
|
+
else setError(r && r.error ? r.error.message : "预览失败");
|
|
4569
|
+
});
|
|
4570
|
+
const execRename = () => run(async () => {
|
|
4571
|
+
if (!renaming) return;
|
|
4572
|
+
if (!registry.some((e) => e.slug === renaming.slug)) {
|
|
4573
|
+
await rpc("dsh-memory/room-register", { slug: renaming.slug, source: "grown" });
|
|
4574
|
+
}
|
|
4575
|
+
const r = await rpc("dsh-memory/room-admin", { action: "rename", from: renaming.slug, to: renaming.to.trim(), dryRun: false });
|
|
4576
|
+
if (r && r.ok) {
|
|
4577
|
+
setMsg(String(r.value?.notice ?? "完成"));
|
|
4578
|
+
setRenaming(null);
|
|
4579
|
+
loadAll();
|
|
4580
|
+
} else setError(r && r.error ? r.error.message : "执行失败");
|
|
4581
|
+
});
|
|
4582
|
+
const saveEditing = () => run(async () => {
|
|
4583
|
+
if (!editing) return;
|
|
4584
|
+
const r = await rpc("dsh-memory/room-admin", { action: "update", slug: editing.slug, label: editing.label, description: editing.description });
|
|
4585
|
+
if (r && r.ok) {
|
|
4586
|
+
setMsg(String(r.value?.notice ?? "已更新"));
|
|
4587
|
+
setEditing(null);
|
|
4588
|
+
loadAll();
|
|
4589
|
+
} else setError(r && r.error ? r.error.message : "更新失败");
|
|
4590
|
+
});
|
|
4591
|
+
const retire = (slug, active, withRecords = false) => run(async () => {
|
|
4592
|
+
if (!active) {
|
|
4593
|
+
const tip = withRecords ? `退役 Room:${slug} 并退场其下全部记忆?(记录为软删,可在记忆页「已退场」区逐条恢复)` : `退役 Room:${slug}?(存量记录的 tags 不动,仅词表不再推荐;可随时恢复)`;
|
|
4594
|
+
if (!window.confirm(tip)) return;
|
|
4595
|
+
}
|
|
4596
|
+
const r = await rpc("dsh-memory/room-admin", { action: "retire", slug, active, withRecords: withRecords || void 0 });
|
|
4597
|
+
if (r && r.ok) {
|
|
4598
|
+
setMsg(String(r.value?.notice ?? "完成"));
|
|
4599
|
+
loadAll();
|
|
4600
|
+
} else setError(r && r.error ? r.error.message : "操作失败");
|
|
4601
|
+
});
|
|
4602
|
+
const exportRecords = (slug) => run(async () => {
|
|
4603
|
+
const r = await rpc("dsh-memory/rooms-export", { kind: "records", tag: slug });
|
|
4604
|
+
if (!r.ok) {
|
|
4605
|
+
setError(r.error.message);
|
|
4606
|
+
return;
|
|
4607
|
+
}
|
|
4608
|
+
if (r.value.csv) {
|
|
4609
|
+
const blob = new Blob([r.value.csv], { type: "text/csv;charset=utf-8" });
|
|
4610
|
+
const url = URL.createObjectURL(blob);
|
|
4611
|
+
const a = document.createElement("a");
|
|
4612
|
+
a.href = url;
|
|
4613
|
+
a.download = `dsh-memory-room-${slug.replace(/\//g, "-")}-records.csv`;
|
|
4614
|
+
a.click();
|
|
4615
|
+
URL.revokeObjectURL(url);
|
|
4616
|
+
}
|
|
4617
|
+
});
|
|
4618
|
+
const toggleExpand = (slug) => run(async () => {
|
|
4619
|
+
if (expanded.has(slug)) {
|
|
4620
|
+
const next = new Set(expanded);
|
|
4621
|
+
next.delete(slug);
|
|
4622
|
+
setExpanded(next);
|
|
4623
|
+
return;
|
|
4624
|
+
}
|
|
4625
|
+
const r = await rpc("dsh-memory/list-records", { tag: slug, limit: 10 });
|
|
4626
|
+
if (r && r.ok) {
|
|
4627
|
+
const next = new Set(expanded);
|
|
4628
|
+
next.add(slug);
|
|
4629
|
+
setExpanded(next);
|
|
4630
|
+
setRecords((m) => ({ ...m, [slug]: { items: r.value.items, total: r.value.total ?? null } }));
|
|
4631
|
+
} else setError(r && r.error ? r.error.message : "加载失败");
|
|
4632
|
+
});
|
|
4633
|
+
const countOf = (slug) => counts.find((c) => c.room === slug)?.count ?? 0;
|
|
4634
|
+
const rows = [
|
|
4635
|
+
...registry.map((e) => ({ slug: e.slug, count: countOf(e.slug), entry: e })),
|
|
4636
|
+
...counts.filter((c) => !registry.some((e) => e.slug === c.room)).map((c) => ({ slug: c.room, count: c.count }))
|
|
4637
|
+
];
|
|
4638
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { children: [
|
|
4639
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { ...S.flexRow, marginBottom: 8, flexWrap: "wrap" }, children: [
|
|
4640
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: S.muted, children: "Room 级管理:存在(注册/收编/退役)· 名称(显示名/说明/改名)· 条目(展开浏览) · 归类到 hall 请到「Hall」页" }),
|
|
4641
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: S.grow }),
|
|
4642
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("button", { type: "button", style: btn2, title: "导出无 Room 绑定的孤儿清单(含候选)", onClick: () => run(async () => {
|
|
4643
|
+
const r = await rpc("dsh-memory/rooms-export", { kind: "orphans" });
|
|
4644
|
+
if (!r.ok) {
|
|
4645
|
+
setError(r.error.message);
|
|
4646
|
+
return;
|
|
4647
|
+
}
|
|
4648
|
+
if (r.value.csv) downloadCsv2(r.value.csv, "dsh-memory-room-orphans.csv");
|
|
4649
|
+
}), children: [
|
|
4650
|
+
"导出孤儿 · ",
|
|
4651
|
+
orphanCount
|
|
4652
|
+
] }),
|
|
4653
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
4654
|
+
"button",
|
|
4655
|
+
{
|
|
4656
|
+
type: "button",
|
|
4657
|
+
style: { ...btn2, color: hiPriv ? "var(--dsh-mem-danger)" : void 0, border: hiPriv ? "1px solid var(--dsh-mem-danger)" : btn2.border },
|
|
4658
|
+
disabled: hiPrivBusy,
|
|
4659
|
+
title: hiPriv ? "关闭高权限模式" : "开启高权限模式以解锁 Room 管理操作",
|
|
4660
|
+
onClick: toggleHiPriv,
|
|
4661
|
+
children: hiPriv ? "高权限:开" : "高权限:关"
|
|
4662
|
+
}
|
|
4663
|
+
),
|
|
4664
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", style: btn2, onClick: loadAll, children: "刷新" })
|
|
4665
|
+
] }),
|
|
4666
|
+
editing ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { position: "sticky", top: 0, zIndex: 5, background: "var(--dsh-mem-bg-inset)", display: "flex", alignItems: "center", gap: 6, marginBottom: 8, flexWrap: "wrap", padding: "6px 8px", border: "1px solid var(--dsh-mem-accent)", borderRadius: 8 }, children: [
|
|
4667
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { style: S.muted, children: [
|
|
4668
|
+
"编辑 ",
|
|
4669
|
+
editing.slug
|
|
4670
|
+
] }),
|
|
4671
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("input", { value: editing.label, onChange: (e) => setEditing({ ...editing, label: e.target.value }), placeholder: "显示名(可中文)", disabled: busy, style: { ...S.input, width: 140, fontSize: 12 } }),
|
|
4672
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("input", { value: editing.description, onChange: (e) => setEditing({ ...editing, description: e.target.value }), placeholder: "归类说明(喂标注器)", disabled: busy, style: { ...S.input, width: 200, fontSize: 12 } }),
|
|
4673
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", disabled: busy, onClick: saveEditing, style: { ...btn2, color: "var(--dsh-mem-accent)", borderColor: "var(--dsh-mem-accent)" }, children: "保存" }),
|
|
4674
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", disabled: busy, onClick: () => setEditing(null), style: btn2, children: "取消" })
|
|
4675
|
+
] }) : null,
|
|
4676
|
+
renaming ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { position: "sticky", top: 0, zIndex: 5, background: "var(--dsh-mem-bg-inset)", display: "flex", alignItems: "center", gap: 6, marginBottom: 8, flexWrap: "wrap", padding: "6px 8px", border: "1px solid var(--dsh-mem-accent)", borderRadius: 8 }, children: [
|
|
4677
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { style: S.muted, children: [
|
|
4678
|
+
"改名 ",
|
|
4679
|
+
renaming.slug,
|
|
4680
|
+
" →"
|
|
4681
|
+
] }),
|
|
4682
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("input", { value: renaming.to, onChange: (e) => setRenaming({ ...renaming, to: e.target.value }), placeholder: "新 slug(可两级:hall/子类)", disabled: busy, style: { ...S.input, width: 200, fontSize: 12 } }),
|
|
4683
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", disabled: busy, onClick: previewRename, style: { ...btn2, color: "var(--dsh-mem-accent)", borderColor: "var(--dsh-mem-accent)" }, children: "预览" }),
|
|
4684
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", disabled: busy, onClick: execRename, style: { ...btn2, color: "var(--dsh-mem-danger)", borderColor: "var(--dsh-mem-danger)" }, children: "确认执行" }),
|
|
4685
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", disabled: busy, onClick: () => setRenaming(null), style: btn2, children: "取消" }),
|
|
4686
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: S.muted, children: "记录随迁;与反刍并发会互相覆盖,请在反刍空闲时执行。" })
|
|
4687
|
+
] }) : null,
|
|
4688
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, alignItems: "center", marginBottom: 10 }, children: [
|
|
4689
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("input", { value: reg.slug, onChange: (e) => setReg({ ...reg, slug: e.target.value }), placeholder: "slug(如 dsh-plugin 或 dsh-plugin/merge)", disabled: !hiPriv || busy, style: { ...S.input, width: 210, fontSize: 12 } }),
|
|
4690
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("input", { value: reg.label, onChange: (e) => setReg({ ...reg, label: e.target.value }), placeholder: "名称(可中文)", disabled: !hiPriv || busy, style: { ...S.input, width: 110, fontSize: 12 } }),
|
|
4691
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("input", { value: reg.description, onChange: (e) => setReg({ ...reg, description: e.target.value }), placeholder: "归类说明(喂标注器)", disabled: !hiPriv || busy, style: { ...S.input, width: 170, fontSize: 12 } }),
|
|
4692
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", disabled: !hiPriv || busy || !reg.slug.trim(), onClick: register, style: { ...btn2, color: "var(--dsh-mem-accent)", borderColor: "var(--dsh-mem-accent)", opacity: !hiPriv || busy || !reg.slug.trim() ? 0.5 : 1 }, children: "注册" }),
|
|
4693
|
+
!hiPriv && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: S.muted, children: "管理操作需先开启高权限模式" })
|
|
4694
|
+
] }),
|
|
4695
|
+
msg ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: S.hint, children: msg }) : null,
|
|
4696
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: S.error, children: error }) : null,
|
|
4697
|
+
rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { style: S.intro, children: "暂无 Room:对话/反刍会从标签涌现,或在上方注册。" }) : rows.map((row2) => {
|
|
4698
|
+
const e = row2.entry;
|
|
4699
|
+
const retired = e?.status === "retired";
|
|
4700
|
+
const grownOnly = !e;
|
|
4701
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { marginBottom: 6, border: "1px solid var(--dsh-mem-border)", borderRadius: 8 }, children: [
|
|
4702
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8, padding: "4px 10px" }, children: [
|
|
4703
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
4704
|
+
"button",
|
|
4705
|
+
{
|
|
4706
|
+
type: "button",
|
|
4707
|
+
title: expanded.has(row2.slug) ? "收起记忆条目" : "展开查看该 Room 的记忆条目(默认收起)",
|
|
4708
|
+
onClick: () => void toggleExpand(row2.slug),
|
|
4709
|
+
style: { ...btn2, borderRadius: 999, padding: "1px 7px", flexShrink: 0 },
|
|
4710
|
+
children: expanded.has(row2.slug) ? "▾" : "▸"
|
|
4711
|
+
}
|
|
4712
|
+
),
|
|
4713
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { flex: 1, minWidth: 0, display: "flex", flexWrap: "wrap", gap: "2px 8px", alignItems: "baseline" }, children: [
|
|
4714
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { style: { fontSize: 12, opacity: retired ? 0.6 : 1 }, children: [
|
|
4715
|
+
row2.slug,
|
|
4716
|
+
e?.source === "pre-registered" ? " ⭐" : ""
|
|
4717
|
+
] }),
|
|
4718
|
+
e?.label ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: S.muted, children: e.label }) : null,
|
|
4719
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: S.muted, children: row2.count + " 条" }),
|
|
4720
|
+
grownOnly ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: S.muted, children: "(自生长未注册)" }) : null,
|
|
4721
|
+
retired ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: { ...S.muted, color: "var(--dsh-mem-danger)" }, children: "已退役" }) : null,
|
|
4722
|
+
(e?.aliases ?? []).length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: { ...S.muted, wordBreak: "break-all" }, children: "别名: " + (e?.aliases ?? []).join(", ") }) : null
|
|
4723
|
+
] }),
|
|
4724
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { display: "flex", gap: 6, flexShrink: 0, alignItems: "center" }, children: [
|
|
4725
|
+
!hiPriv || grownOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
|
|
4726
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", disabled: busy, title: "编辑显示名/归类说明", onClick: () => {
|
|
4727
|
+
setEditing({ slug: row2.slug, label: e?.label ?? "", description: e?.description ?? "" });
|
|
4728
|
+
setRenaming(null);
|
|
4729
|
+
}, style: btn2, children: "编辑" }),
|
|
4730
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", disabled: busy, title: "改名(可两级 slug;记录随迁,旧名进别名)", onClick: () => {
|
|
4731
|
+
setRenaming({ slug: row2.slug, to: "" });
|
|
4732
|
+
setEditing(null);
|
|
4733
|
+
}, style: btn2, children: "改名" }),
|
|
4734
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
4735
|
+
"button",
|
|
4736
|
+
{
|
|
4737
|
+
type: "button",
|
|
4738
|
+
disabled: busy,
|
|
4739
|
+
title: retired ? "恢复该 Room 到词表" : "退役该 Room(存量 tags 不动)",
|
|
4740
|
+
onClick: () => retire(row2.slug, retired),
|
|
4741
|
+
style: { ...btn2, ...retired ? { color: "var(--dsh-mem-accent)", borderColor: "var(--dsh-mem-accent)" } : { color: "var(--dsh-mem-danger)", borderColor: "var(--dsh-mem-danger)" } },
|
|
4742
|
+
children: retired ? "恢复" : "退役"
|
|
4743
|
+
}
|
|
4744
|
+
),
|
|
4745
|
+
!retired ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", disabled: busy, title: "退役并退场该 Room 下全部记忆(软删,可在「已退场」区恢复)", onClick: () => retire(row2.slug, false, true), style: { ...btn2, color: "var(--dsh-mem-danger)", borderColor: "var(--dsh-mem-danger)" }, children: "退场" }) : null
|
|
4746
|
+
] }),
|
|
4747
|
+
grownOnly ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
|
|
4748
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", disabled: !hiPriv || busy, title: "收编进注册表(source=grown)", onClick: () => adopt(row2.slug), style: { ...btn2, color: "var(--dsh-mem-accent)", borderColor: "var(--dsh-mem-accent)", opacity: !hiPriv || busy ? 0.5 : 1 }, children: "收编" }),
|
|
4749
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", disabled: !hiPriv || busy, title: "退场该 Room 下全部记忆(软删,可在「已退场」区恢复)", onClick: () => {
|
|
4750
|
+
setRenaming(null);
|
|
4751
|
+
retire(row2.slug, false, true);
|
|
4752
|
+
}, style: { ...btn2, color: "var(--dsh-mem-danger)", borderColor: "var(--dsh-mem-danger)", opacity: !hiPriv || busy ? 0.5 : 1 }, children: "退场" })
|
|
4753
|
+
] }) : null,
|
|
4754
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", disabled: busy, title: "导出该 Room 的记录清单 CSV", onClick: () => exportRecords(row2.slug), style: btn2, children: "导出" })
|
|
4755
|
+
] })
|
|
4756
|
+
] }),
|
|
4757
|
+
expanded.has(row2.slug) ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { margin: "0 10px 6px", padding: "4px 8px", border: "1px dashed var(--dsh-mem-border)", borderRadius: 6 }, children: [
|
|
4758
|
+
(records[row2.slug]?.items ?? []).map((it) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { fontSize: 11, padding: "3px 0", borderBottom: "1px solid var(--dsh-mem-border)" }, children: [
|
|
4759
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "dsh-mem-tag dsh-mem-tag-" + it.type, children: TYPE_LABELS[it.type] || it.type }),
|
|
4760
|
+
" ",
|
|
4761
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: S.muted, children: it.updatedAt ? fmtTime(it.updatedAt) : "" }),
|
|
4762
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: { marginTop: 2, wordBreak: "break-all", color: "var(--dsh-mem-text-2)" }, children: it.content.length > 140 ? it.content.slice(0, 140) + "…" : it.content })
|
|
4763
|
+
] }, it.id)),
|
|
4764
|
+
(records[row2.slug]?.items.length ?? 0) === 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: S.muted, children: "该 Room 暂无记忆条目。" }) : null,
|
|
4765
|
+
(records[row2.slug]?.total ?? 0) > (records[row2.slug]?.items.length ?? 0) ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: { ...S.muted, fontSize: 11 }, children: "共 " + records[row2.slug].total + " 条,仅显示前 " + records[row2.slug].items.length + " 条" }) : null
|
|
4766
|
+
] }) : null
|
|
4767
|
+
] }, row2.slug);
|
|
4768
|
+
})
|
|
4769
|
+
] });
|
|
4770
|
+
}
|
|
4771
|
+
|
|
4772
|
+
// client/src/tabs/ScenesTab.tsx
|
|
4773
|
+
var import_react18 = require("react");
|
|
4774
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
3780
4775
|
function SceneCard(props) {
|
|
3781
4776
|
const s = props.s;
|
|
3782
|
-
const [open, setOpen] = (0,
|
|
3783
|
-
return /* @__PURE__ */ (0,
|
|
3784
|
-
/* @__PURE__ */ (0,
|
|
4777
|
+
const [open, setOpen] = (0, import_react18.useState)(false);
|
|
4778
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "dsh-mem-card dsh-mem-card-hover", style: S.card, children: [
|
|
4779
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
|
|
3785
4780
|
"div",
|
|
3786
4781
|
{
|
|
3787
4782
|
style: { ...S.sceneHead, cursor: "pointer", userSelect: "none" },
|
|
@@ -3789,23 +4784,23 @@ var __defProp = Object.defineProperty;
|
|
|
3789
4784
|
setOpen(!open);
|
|
3790
4785
|
},
|
|
3791
4786
|
children: [
|
|
3792
|
-
/* @__PURE__ */ (0,
|
|
3793
|
-
/* @__PURE__ */ (0,
|
|
3794
|
-
s.heat ? /* @__PURE__ */ (0,
|
|
3795
|
-
/* @__PURE__ */ (0,
|
|
3796
|
-
/* @__PURE__ */ (0,
|
|
4787
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "dsh-mem-scene-chev", style: { transform: open ? "rotate(90deg)" : "none" }, children: "▸" }),
|
|
4788
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { style: S.sceneTitle, children: s.path }),
|
|
4789
|
+
s.heat ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { style: S.muted, children: "热度 " + s.heat }) : null,
|
|
4790
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: S.grow }),
|
|
4791
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { style: S.muted, children: "更新 " + fmtTime(s.updated) })
|
|
3797
4792
|
]
|
|
3798
4793
|
}
|
|
3799
4794
|
),
|
|
3800
|
-
s.summary ? /* @__PURE__ */ (0,
|
|
3801
|
-
open ? /* @__PURE__ */ (0,
|
|
4795
|
+
s.summary ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { ...S.muted, marginBottom: 6 }, children: s.summary }) : null,
|
|
4796
|
+
open ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("pre", { style: S.pre, children: s.content || "(空)" }) : null
|
|
3802
4797
|
] });
|
|
3803
4798
|
}
|
|
3804
4799
|
function ScenesTab(props) {
|
|
3805
4800
|
const rpc = props.rpc;
|
|
3806
|
-
const [items, setItems] = (0,
|
|
3807
|
-
const [error, setError] = (0,
|
|
3808
|
-
const load = (0,
|
|
4801
|
+
const [items, setItems] = (0, import_react18.useState)(null);
|
|
4802
|
+
const [error, setError] = (0, import_react18.useState)(null);
|
|
4803
|
+
const load = (0, import_react18.useCallback)(() => {
|
|
3809
4804
|
rpc("dsh-memory/scenes", {}).then((r) => {
|
|
3810
4805
|
if (r && r.ok) {
|
|
3811
4806
|
setItems(r.value.items);
|
|
@@ -3815,54 +4810,58 @@ var __defProp = Object.defineProperty;
|
|
|
3815
4810
|
setError(String(e && e.message || e));
|
|
3816
4811
|
});
|
|
3817
4812
|
}, [rpc]);
|
|
3818
|
-
(0,
|
|
4813
|
+
(0, import_react18.useEffect)(() => {
|
|
3819
4814
|
load();
|
|
3820
4815
|
}, [load]);
|
|
3821
|
-
return /* @__PURE__ */ (0,
|
|
3822
|
-
/* @__PURE__ */ (0,
|
|
3823
|
-
/* @__PURE__ */ (0,
|
|
3824
|
-
/* @__PURE__ */ (0,
|
|
3825
|
-
/* @__PURE__ */ (0,
|
|
4816
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
|
|
4817
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { ...S.flexRow, marginBottom: 10 }, children: [
|
|
4818
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { style: S.muted, children: items ? items.length + " 个场景块" : "加载中…" }),
|
|
4819
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: S.grow }),
|
|
4820
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(NButton, { onClick: load, children: "刷新" })
|
|
3826
4821
|
] }),
|
|
3827
|
-
error ? /* @__PURE__ */ (0,
|
|
3828
|
-
items && items.length === 0 ? /* @__PURE__ */ (0,
|
|
4822
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: S.error, children: error }) : null,
|
|
4823
|
+
items && items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { style: S.intro, children: "暂无场景块。累计 5 条新记忆后 L2 会自动整合出第一个场景。" }) : null,
|
|
3829
4824
|
(items || []).map((s) => {
|
|
3830
|
-
return /* @__PURE__ */ (0,
|
|
4825
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SceneCard, { s }, s.path);
|
|
3831
4826
|
})
|
|
3832
4827
|
] });
|
|
3833
4828
|
}
|
|
3834
4829
|
|
|
3835
4830
|
// client/src/panel.tsx
|
|
3836
|
-
var
|
|
4831
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
3837
4832
|
var TABS = [
|
|
3838
4833
|
["overview", "概览"],
|
|
3839
4834
|
["records", "记忆"],
|
|
3840
4835
|
["conflicts", "冲突"],
|
|
3841
4836
|
["scenes", "场景"],
|
|
4837
|
+
["rooms", "Hall"],
|
|
4838
|
+
["room", "Room"],
|
|
3842
4839
|
["persona", "画像"],
|
|
3843
4840
|
["cost", "成本"],
|
|
3844
4841
|
["log", "日志"]
|
|
3845
4842
|
];
|
|
3846
4843
|
function MemoryPanel(props) {
|
|
3847
4844
|
const rpc = props.rpc;
|
|
3848
|
-
const [tab, setTab] = (0,
|
|
4845
|
+
const [tab, setTab] = (0, import_react19.useState)("overview");
|
|
3849
4846
|
ensureThemeStyle();
|
|
3850
|
-
(0,
|
|
4847
|
+
(0, import_react19.useEffect)(() => {
|
|
3851
4848
|
watchSidebarIcon();
|
|
3852
4849
|
}, []);
|
|
3853
4850
|
let body;
|
|
3854
|
-
if (tab === "overview") body = /* @__PURE__ */ (0,
|
|
3855
|
-
else if (tab === "records") body = /* @__PURE__ */ (0,
|
|
3856
|
-
else if (tab === "conflicts") body = /* @__PURE__ */ (0,
|
|
3857
|
-
else if (tab === "scenes") body = /* @__PURE__ */ (0,
|
|
3858
|
-
else if (tab === "
|
|
3859
|
-
else if (tab === "
|
|
3860
|
-
else body = /* @__PURE__ */ (0,
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
/* @__PURE__ */ (0,
|
|
3865
|
-
|
|
4851
|
+
if (tab === "overview") body = /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(OverviewTab, { rpc });
|
|
4852
|
+
else if (tab === "records") body = /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(RecordsTab, { rpc });
|
|
4853
|
+
else if (tab === "conflicts") body = /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ConflictsTab, { rpc });
|
|
4854
|
+
else if (tab === "scenes") body = /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ScenesTab, { rpc });
|
|
4855
|
+
else if (tab === "rooms") body = /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(RoomsTab, { rpc });
|
|
4856
|
+
else if (tab === "room") body = /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(RoomTab, { rpc });
|
|
4857
|
+
else if (tab === "persona") body = /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(PersonaTab, { rpc });
|
|
4858
|
+
else if (tab === "cost") body = /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(CostTab, { rpc });
|
|
4859
|
+
else body = /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(LogTab, { rpc });
|
|
4860
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "dsh-mem-root", style: S.section, children: [
|
|
4861
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h2", { style: S.heading, children: "记忆 (Memory)" }),
|
|
4862
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { style: S.intro, children: "L0~L3 分层蒸馏记忆:浏览被记住的内容,控制记忆模式开关。" }),
|
|
4863
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { style: S.tabbar, children: TABS.map((t) => {
|
|
4864
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
3866
4865
|
"button",
|
|
3867
4866
|
{
|
|
3868
4867
|
className: tab === t[0] ? "dsh-mem-tab dsh-mem-tab-on" : "dsh-mem-tab",
|
|
@@ -3879,7 +4878,7 @@ var __defProp = Object.defineProperty;
|
|
|
3879
4878
|
}
|
|
3880
4879
|
|
|
3881
4880
|
// client/src/pill/MemoryModePill.tsx
|
|
3882
|
-
var
|
|
4881
|
+
var import_react22 = require("react");
|
|
3883
4882
|
|
|
3884
4883
|
// src/util/context-occupancy.ts
|
|
3885
4884
|
var CONTEXT_METER_CIRCUMFERENCE = 34.55751918948772;
|
|
@@ -4195,23 +5194,23 @@ var __defProp = Object.defineProperty;
|
|
|
4195
5194
|
}
|
|
4196
5195
|
|
|
4197
5196
|
// client/src/pill/WingWheel.tsx
|
|
4198
|
-
var
|
|
5197
|
+
var import_react21 = require("react");
|
|
4199
5198
|
|
|
4200
5199
|
// client/src/pill/SessionInfoArea.tsx
|
|
4201
|
-
var
|
|
4202
|
-
var
|
|
5200
|
+
var import_react20 = require("react");
|
|
5201
|
+
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
4203
5202
|
function sinfoCell(val, label, title) {
|
|
4204
|
-
return /* @__PURE__ */ (0,
|
|
4205
|
-
/* @__PURE__ */ (0,
|
|
4206
|
-
/* @__PURE__ */ (0,
|
|
5203
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { title: title || void 0, children: [
|
|
5204
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "dsh-mem-sinfo-val", children: val }),
|
|
5205
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "dsh-mem-sinfo-label", children: label })
|
|
4207
5206
|
] });
|
|
4208
5207
|
}
|
|
4209
5208
|
function SessionInfoArea(props) {
|
|
4210
5209
|
const rpc = props.rpc;
|
|
4211
5210
|
const sessionId = props.sessionId;
|
|
4212
|
-
const [stats, setStats] = (0,
|
|
4213
|
-
const busyRef = (0,
|
|
4214
|
-
(0,
|
|
5211
|
+
const [stats, setStats] = (0, import_react20.useState)(void 0);
|
|
5212
|
+
const busyRef = (0, import_react20.useRef)(false);
|
|
5213
|
+
(0, import_react20.useEffect)(() => {
|
|
4215
5214
|
if (!rpc || !sessionId) return void 0;
|
|
4216
5215
|
let alive = true;
|
|
4217
5216
|
let timer = null;
|
|
@@ -4244,7 +5243,7 @@ var __defProp = Object.defineProperty;
|
|
|
4244
5243
|
}, [rpc, sessionId]);
|
|
4245
5244
|
if (stats === null) return null;
|
|
4246
5245
|
if (stats === void 0) {
|
|
4247
|
-
return /* @__PURE__ */ (0,
|
|
5246
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "dsh-mem-sinfo", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "dsh-mem-sinfo-grid", children: [
|
|
4248
5247
|
sinfoCell("…", "召回命中"),
|
|
4249
5248
|
sinfoCell("…", "攒批进度"),
|
|
4250
5249
|
sinfoCell("…", "本会话记忆"),
|
|
@@ -4293,21 +5292,21 @@ var __defProp = Object.defineProperty;
|
|
|
4293
5292
|
else if (stats.retrieval === "none") note = "检索不可用(FTS 与向量均失效)";
|
|
4294
5293
|
}
|
|
4295
5294
|
const ago = fmtAgo(gl.lastExtractAt);
|
|
4296
|
-
return /* @__PURE__ */ (0,
|
|
4297
|
-
warn ? /* @__PURE__ */ (0,
|
|
4298
|
-
/* @__PURE__ */ (0,
|
|
5295
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "dsh-mem-sinfo", children: [
|
|
5296
|
+
warn ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "dsh-mem-sinfo-warn", children: warn }) : null,
|
|
5297
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "dsh-mem-sinfo-grid", children: [
|
|
4299
5298
|
sinfoCell(rcVal, rcLabel, rcTitle),
|
|
4300
5299
|
sinfoCell(dVal, dLabel, dTitle),
|
|
4301
5300
|
sinfoCell(String(di.producedRecords || 0), "本会话记忆", pTitle),
|
|
4302
5301
|
sinfoCell(stats.l0Count != null ? String(stats.l0Count) : "…", "会话消息")
|
|
4303
5302
|
] }),
|
|
4304
|
-
note ? /* @__PURE__ */ (0,
|
|
4305
|
-
/* @__PURE__ */ (0,
|
|
5303
|
+
note ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "dsh-mem-sinfo-note", children: note }) : null,
|
|
5304
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "dsh-mem-sinfo-sum", children: "待蒸馏 " + (gl.pendingTotal || 0) + " · 上次蒸馏 " + (ago || "尚未蒸馏") })
|
|
4306
5305
|
] });
|
|
4307
5306
|
}
|
|
4308
5307
|
|
|
4309
5308
|
// client/src/pill/WingWheel.tsx
|
|
4310
|
-
var
|
|
5309
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
4311
5310
|
var SIZE = 196;
|
|
4312
5311
|
var CENTER = SIZE / 2;
|
|
4313
5312
|
var R = 64;
|
|
@@ -4350,17 +5349,17 @@ var __defProp = Object.defineProperty;
|
|
|
4350
5349
|
function WingWheel(props) {
|
|
4351
5350
|
ensureThemeStyle();
|
|
4352
5351
|
const isOff = props.mode === "off";
|
|
4353
|
-
const [overview, setOverview] = (0,
|
|
4354
|
-
const [backfillBusy, setBackfillBusy] = (0,
|
|
4355
|
-
const [localError, setLocalError] = (0,
|
|
4356
|
-
const timersRef = (0,
|
|
4357
|
-
(0,
|
|
5352
|
+
const [overview, setOverview] = (0, import_react21.useState)(null);
|
|
5353
|
+
const [backfillBusy, setBackfillBusy] = (0, import_react21.useState)(false);
|
|
5354
|
+
const [localError, setLocalError] = (0, import_react21.useState)(null);
|
|
5355
|
+
const timersRef = (0, import_react21.useRef)([]);
|
|
5356
|
+
(0, import_react21.useEffect)(
|
|
4358
5357
|
() => () => {
|
|
4359
5358
|
for (const t of timersRef.current) window.clearTimeout(t);
|
|
4360
5359
|
},
|
|
4361
5360
|
[]
|
|
4362
5361
|
);
|
|
4363
|
-
(0,
|
|
5362
|
+
(0, import_react21.useEffect)(() => {
|
|
4364
5363
|
let alive = true;
|
|
4365
5364
|
props.rpc("dsh-memory/wing-overview", {}).then((r) => {
|
|
4366
5365
|
if (!alive) return;
|
|
@@ -4406,28 +5405,28 @@ var __defProp = Object.defineProperty;
|
|
|
4406
5405
|
finish();
|
|
4407
5406
|
});
|
|
4408
5407
|
};
|
|
4409
|
-
const boxRef = (0,
|
|
4410
|
-
const startRef = (0,
|
|
4411
|
-
const movedRef = (0,
|
|
4412
|
-
const downIndexRef = (0,
|
|
4413
|
-
const dragTargetRef = (0,
|
|
4414
|
-
const [drag, setDrag] = (0,
|
|
5408
|
+
const boxRef = (0, import_react21.useRef)(null);
|
|
5409
|
+
const startRef = (0, import_react21.useRef)(null);
|
|
5410
|
+
const movedRef = (0, import_react21.useRef)(false);
|
|
5411
|
+
const downIndexRef = (0, import_react21.useRef)(null);
|
|
5412
|
+
const dragTargetRef = (0, import_react21.useRef)(null);
|
|
5413
|
+
const [drag, setDrag] = (0, import_react21.useState)(null);
|
|
4415
5414
|
const lockedIndex = (() => {
|
|
4416
5415
|
if (props.halls.length !== 1 || !overview) return null;
|
|
4417
5416
|
const i = overview.corners.findIndex((c) => c.id === props.halls[0]);
|
|
4418
5417
|
return i >= 0 ? i : null;
|
|
4419
5418
|
})();
|
|
4420
5419
|
const activeCorner = drag ? drag.index : lockedIndex;
|
|
4421
|
-
const blockPosRef = (0,
|
|
5420
|
+
const blockPosRef = (0, import_react21.useRef)(
|
|
4422
5421
|
activeCorner != null ? cornerXY(activeCorner) : { x: CENTER, y: CENTER }
|
|
4423
5422
|
);
|
|
4424
|
-
const blockTargetRef = (0,
|
|
4425
|
-
const [blockPos, setBlockPos] = (0,
|
|
4426
|
-
(0,
|
|
5423
|
+
const blockTargetRef = (0, import_react21.useRef)(blockPosRef.current);
|
|
5424
|
+
const [blockPos, setBlockPos] = (0, import_react21.useState)(blockPosRef.current);
|
|
5425
|
+
(0, import_react21.useEffect)(() => {
|
|
4427
5426
|
blockTargetRef.current = activeCorner != null ? cornerXY(activeCorner) : { x: CENTER, y: CENTER };
|
|
4428
5427
|
}, [activeCorner]);
|
|
4429
|
-
const reducedMotionRef = (0,
|
|
4430
|
-
(0,
|
|
5428
|
+
const reducedMotionRef = (0, import_react21.useRef)(false);
|
|
5429
|
+
(0, import_react21.useEffect)(() => {
|
|
4431
5430
|
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
4432
5431
|
const apply2 = () => {
|
|
4433
5432
|
reducedMotionRef.current = mq.matches;
|
|
@@ -4436,7 +5435,7 @@ var __defProp = Object.defineProperty;
|
|
|
4436
5435
|
mq.addEventListener("change", apply2);
|
|
4437
5436
|
return () => mq.removeEventListener("change", apply2);
|
|
4438
5437
|
}, []);
|
|
4439
|
-
(0,
|
|
5438
|
+
(0, import_react21.useEffect)(() => {
|
|
4440
5439
|
let raf = 0;
|
|
4441
5440
|
const loop = () => {
|
|
4442
5441
|
const cur = blockPosRef.current;
|
|
@@ -4525,8 +5524,8 @@ var __defProp = Object.defineProperty;
|
|
|
4525
5524
|
}).join(" ");
|
|
4526
5525
|
const grayStyle = isOff ? { opacity: 0.45, pointerEvents: "none" } : {};
|
|
4527
5526
|
const boundariesDisabled = props.halls.length === 0 || isOff;
|
|
4528
|
-
return /* @__PURE__ */ (0,
|
|
4529
|
-
/* @__PURE__ */ (0,
|
|
5527
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { style: { width: SIZE }, children: [
|
|
5528
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
4530
5529
|
"div",
|
|
4531
5530
|
{
|
|
4532
5531
|
ref: boxRef,
|
|
@@ -4536,7 +5535,7 @@ var __defProp = Object.defineProperty;
|
|
|
4536
5535
|
onPointerUp: onBoxUp,
|
|
4537
5536
|
onPointerCancel: onBoxUp,
|
|
4538
5537
|
children: [
|
|
4539
|
-
/* @__PURE__ */ (0,
|
|
5538
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
4540
5539
|
"svg",
|
|
4541
5540
|
{
|
|
4542
5541
|
width: SIZE,
|
|
@@ -4544,7 +5543,7 @@ var __defProp = Object.defineProperty;
|
|
|
4544
5543
|
style: { position: "absolute", inset: 0, pointerEvents: "none" },
|
|
4545
5544
|
"aria-hidden": "true",
|
|
4546
5545
|
children: [
|
|
4547
|
-
/* @__PURE__ */ (0,
|
|
5546
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
4548
5547
|
"polygon",
|
|
4549
5548
|
{
|
|
4550
5549
|
points: polyPoints,
|
|
@@ -4555,7 +5554,7 @@ var __defProp = Object.defineProperty;
|
|
|
4555
5554
|
opacity: 0.45
|
|
4556
5555
|
}
|
|
4557
5556
|
),
|
|
4558
|
-
/* @__PURE__ */ (0,
|
|
5557
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
4559
5558
|
"polygon",
|
|
4560
5559
|
{
|
|
4561
5560
|
points: polyPoints,
|
|
@@ -4566,7 +5565,7 @@ var __defProp = Object.defineProperty;
|
|
|
4566
5565
|
),
|
|
4567
5566
|
[0, 1, 2, 3, 4, 5, 6, 7].map((i) => {
|
|
4568
5567
|
const p = cornerPos(i);
|
|
4569
|
-
return /* @__PURE__ */ (0,
|
|
5568
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
4570
5569
|
"line",
|
|
4571
5570
|
{
|
|
4572
5571
|
x1: CENTER,
|
|
@@ -4582,7 +5581,7 @@ var __defProp = Object.defineProperty;
|
|
|
4582
5581
|
]
|
|
4583
5582
|
}
|
|
4584
5583
|
),
|
|
4585
|
-
/* @__PURE__ */ (0,
|
|
5584
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
4586
5585
|
"div",
|
|
4587
5586
|
{
|
|
4588
5587
|
"aria-hidden": "true",
|
|
@@ -4601,7 +5600,7 @@ var __defProp = Object.defineProperty;
|
|
|
4601
5600
|
}
|
|
4602
5601
|
}
|
|
4603
5602
|
),
|
|
4604
|
-
/* @__PURE__ */ (0,
|
|
5603
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
4605
5604
|
"button",
|
|
4606
5605
|
{
|
|
4607
5606
|
type: "button",
|
|
@@ -4635,7 +5634,7 @@ var __defProp = Object.defineProperty;
|
|
|
4635
5634
|
const dim = activeCorner != null && !active;
|
|
4636
5635
|
const empty = count === 0;
|
|
4637
5636
|
const p = cornerPos(i);
|
|
4638
|
-
return /* @__PURE__ */ (0,
|
|
5637
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
4639
5638
|
"div",
|
|
4640
5639
|
{
|
|
4641
5640
|
title: id ? `「${label}」${count ?? 0} 条
|
|
@@ -4664,8 +5663,8 @@ var __defProp = Object.defineProperty;
|
|
|
4664
5663
|
zIndex: 2
|
|
4665
5664
|
},
|
|
4666
5665
|
children: [
|
|
4667
|
-
/* @__PURE__ */ (0,
|
|
4668
|
-
/* @__PURE__ */ (0,
|
|
5666
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: label }),
|
|
5667
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { style: { fontSize: 8, opacity: 0.75, fontVariantNumeric: "tabular-nums" }, children: count === null ? " " : empty ? "空角" : `${count} 条` })
|
|
4669
5668
|
]
|
|
4670
5669
|
},
|
|
4671
5670
|
id ?? i
|
|
@@ -4674,7 +5673,7 @@ var __defProp = Object.defineProperty;
|
|
|
4674
5673
|
]
|
|
4675
5674
|
}
|
|
4676
5675
|
),
|
|
4677
|
-
/* @__PURE__ */ (0,
|
|
5676
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
4678
5677
|
"div",
|
|
4679
5678
|
{
|
|
4680
5679
|
title: props.halls.length === 0 ? "选定某个角(单 Wing)后,这两项才生效" : "锁定 Wing 时两类无角记忆是否参与召回",
|
|
@@ -4688,9 +5687,9 @@ var __defProp = Object.defineProperty;
|
|
|
4688
5687
|
pointerEvents: boundariesDisabled ? "none" : void 0
|
|
4689
5688
|
},
|
|
4690
5689
|
children: [
|
|
4691
|
-
/* @__PURE__ */ (0,
|
|
4692
|
-
/* @__PURE__ */ (0,
|
|
4693
|
-
/* @__PURE__ */ (0,
|
|
5690
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { style: { fontSize: 11, color: "var(--dsh-mem-text-3)" }, children: overview ? `另有 ${overview.unlabeled} 条未打标` : "未打标" }),
|
|
5691
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: [
|
|
5692
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
4694
5693
|
Segmented,
|
|
4695
5694
|
{
|
|
4696
5695
|
value: props.hallIncludeUnlabeled ? "in" : "ex",
|
|
@@ -4701,7 +5700,7 @@ var __defProp = Object.defineProperty;
|
|
|
4701
5700
|
onChange: (key) => props.onCommitWingBoundaries({ includeUnlabeled: key === "in" })
|
|
4702
5701
|
}
|
|
4703
5702
|
),
|
|
4704
|
-
/* @__PURE__ */ (0,
|
|
5703
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
4705
5704
|
Segmented,
|
|
4706
5705
|
{
|
|
4707
5706
|
value: props.hallIncludeGeneral ? "in" : "ex",
|
|
@@ -4716,7 +5715,7 @@ var __defProp = Object.defineProperty;
|
|
|
4716
5715
|
]
|
|
4717
5716
|
}
|
|
4718
5717
|
),
|
|
4719
|
-
/* @__PURE__ */ (0,
|
|
5718
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Row, { label: "会话", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
4720
5719
|
Segmented,
|
|
4721
5720
|
{
|
|
4722
5721
|
value: isOff ? "off" : "on",
|
|
@@ -4731,7 +5730,7 @@ var __defProp = Object.defineProperty;
|
|
|
4731
5730
|
onChange: (key) => props.onCommit(key === "off" ? "off" : "auto")
|
|
4732
5731
|
}
|
|
4733
5732
|
) }),
|
|
4734
|
-
props.recall !== void 0 && props.onCommitRecall ? /* @__PURE__ */ (0,
|
|
5733
|
+
props.recall !== void 0 && props.onCommitRecall ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Row, { label: "注入", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
4735
5734
|
Segmented,
|
|
4736
5735
|
{
|
|
4737
5736
|
value: props.recall === null ? "follow" : props.recall ? "on" : "off",
|
|
@@ -4744,7 +5743,7 @@ var __defProp = Object.defineProperty;
|
|
|
4744
5743
|
onChange: (key) => props.onCommitRecall(key === "on" ? true : key === "off" ? false : null)
|
|
4745
5744
|
}
|
|
4746
5745
|
) }) : null,
|
|
4747
|
-
/* @__PURE__ */ (0,
|
|
5746
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
4748
5747
|
"div",
|
|
4749
5748
|
{
|
|
4750
5749
|
style: {
|
|
@@ -4755,8 +5754,8 @@ var __defProp = Object.defineProperty;
|
|
|
4755
5754
|
marginTop: 10
|
|
4756
5755
|
},
|
|
4757
5756
|
children: [
|
|
4758
|
-
/* @__PURE__ */ (0,
|
|
4759
|
-
/* @__PURE__ */ (0,
|
|
5757
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { style: { fontSize: 11, color: "var(--dsh-mem-text-3)" }, children: overview ? `未打标 ${overview.unlabeled} 条` : "未打标计数加载中" }),
|
|
5758
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
4760
5759
|
ActionButton,
|
|
4761
5760
|
{
|
|
4762
5761
|
label: backfillBusy ? "回填中…" : "回填",
|
|
@@ -4768,12 +5767,12 @@ var __defProp = Object.defineProperty;
|
|
|
4768
5767
|
]
|
|
4769
5768
|
}
|
|
4770
5769
|
),
|
|
4771
|
-
props.error || localError ? /* @__PURE__ */ (0,
|
|
4772
|
-
/* @__PURE__ */ (0,
|
|
5770
|
+
props.error || localError ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { style: { fontSize: 11, color: "var(--dsh-mem-danger)", marginTop: 8 }, children: props.error || localError }) : null,
|
|
5771
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SessionInfoArea, { rpc: props.rpc, sessionId: props.sessionId })
|
|
4773
5772
|
] });
|
|
4774
5773
|
}
|
|
4775
5774
|
function Row(props) {
|
|
4776
|
-
return /* @__PURE__ */ (0,
|
|
5775
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
4777
5776
|
"div",
|
|
4778
5777
|
{
|
|
4779
5778
|
style: {
|
|
@@ -4783,7 +5782,7 @@ var __defProp = Object.defineProperty;
|
|
|
4783
5782
|
marginTop: 10
|
|
4784
5783
|
},
|
|
4785
5784
|
children: [
|
|
4786
|
-
/* @__PURE__ */ (0,
|
|
5785
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { style: { fontSize: 12, color: "var(--dsh-mem-text-3)" }, children: props.label }),
|
|
4787
5786
|
props.children
|
|
4788
5787
|
]
|
|
4789
5788
|
}
|
|
@@ -4791,9 +5790,9 @@ var __defProp = Object.defineProperty;
|
|
|
4791
5790
|
}
|
|
4792
5791
|
var LABEL_FALLBACK = ["工作", "人际", "学习", "创作娱乐", "居家", "健康", "财务", "出行"];
|
|
4793
5792
|
function useViewportClamp(popRef) {
|
|
4794
|
-
const shiftRef = (0,
|
|
4795
|
-
const [shiftX, setShiftX] = (0,
|
|
4796
|
-
(0,
|
|
5793
|
+
const shiftRef = (0, import_react21.useRef)(0);
|
|
5794
|
+
const [shiftX, setShiftX] = (0, import_react21.useState)(0);
|
|
5795
|
+
(0, import_react21.useEffect)(() => {
|
|
4797
5796
|
const clamp = () => {
|
|
4798
5797
|
const el = popRef.current;
|
|
4799
5798
|
if (!el) return;
|
|
@@ -4823,24 +5822,24 @@ var __defProp = Object.defineProperty;
|
|
|
4823
5822
|
}
|
|
4824
5823
|
|
|
4825
5824
|
// client/src/pill/MemoryModePill.tsx
|
|
4826
|
-
var
|
|
5825
|
+
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
4827
5826
|
function MemoryModePill(props) {
|
|
4828
5827
|
const rpc = props.rpc;
|
|
4829
5828
|
const sessionId = props.sessionId || props.session && props.session.sessionId;
|
|
4830
|
-
const [mode, setMode] = (0,
|
|
4831
|
-
const [recall, setRecall] = (0,
|
|
4832
|
-
const [recallResolved, setRecallResolved] = (0,
|
|
4833
|
-
const [halls, setWings] = (0,
|
|
4834
|
-
const [hallIncludeUnlabeled, setWingIncludeUnlabeled] = (0,
|
|
4835
|
-
const [hallIncludeGeneral, setWingIncludeGeneral] = (0,
|
|
4836
|
-
const [wingLabels, setHallLabels] = (0,
|
|
4837
|
-
const [error, setError] = (0,
|
|
4838
|
-
const [open, setOpen] = (0,
|
|
4839
|
-
const wrapRef = (0,
|
|
4840
|
-
const popRef = (0,
|
|
5829
|
+
const [mode, setMode] = (0, import_react22.useState)(null);
|
|
5830
|
+
const [recall, setRecall] = (0, import_react22.useState)(null);
|
|
5831
|
+
const [recallResolved, setRecallResolved] = (0, import_react22.useState)(true);
|
|
5832
|
+
const [halls, setWings] = (0, import_react22.useState)([]);
|
|
5833
|
+
const [hallIncludeUnlabeled, setWingIncludeUnlabeled] = (0, import_react22.useState)(true);
|
|
5834
|
+
const [hallIncludeGeneral, setWingIncludeGeneral] = (0, import_react22.useState)(false);
|
|
5835
|
+
const [wingLabels, setHallLabels] = (0, import_react22.useState)({});
|
|
5836
|
+
const [error, setError] = (0, import_react22.useState)(null);
|
|
5837
|
+
const [open, setOpen] = (0, import_react22.useState)(false);
|
|
5838
|
+
const wrapRef = (0, import_react22.useRef)(null);
|
|
5839
|
+
const popRef = (0, import_react22.useRef)(null);
|
|
4841
5840
|
const shiftX = useViewportClamp(popRef);
|
|
4842
|
-
const seqRef = (0,
|
|
4843
|
-
const load = (0,
|
|
5841
|
+
const seqRef = (0, import_react22.useRef)(0);
|
|
5842
|
+
const load = (0, import_react22.useCallback)(() => {
|
|
4844
5843
|
if (!sessionId || !rpc) return;
|
|
4845
5844
|
const token = ++seqRef.current;
|
|
4846
5845
|
setError(null);
|
|
@@ -4865,13 +5864,13 @@ var __defProp = Object.defineProperty;
|
|
|
4865
5864
|
}).catch(() => {
|
|
4866
5865
|
});
|
|
4867
5866
|
}, [sessionId, rpc]);
|
|
4868
|
-
(0,
|
|
5867
|
+
(0, import_react22.useEffect)(() => {
|
|
4869
5868
|
load();
|
|
4870
5869
|
}, [load]);
|
|
4871
|
-
(0,
|
|
5870
|
+
(0, import_react22.useEffect)(() => {
|
|
4872
5871
|
watchSidebarIcon();
|
|
4873
5872
|
}, []);
|
|
4874
|
-
(0,
|
|
5873
|
+
(0, import_react22.useEffect)(() => {
|
|
4875
5874
|
initOccupancyIndicator(
|
|
4876
5875
|
(endpoint, payload) => rpc(endpoint, payload)
|
|
4877
5876
|
);
|
|
@@ -4879,7 +5878,7 @@ var __defProp = Object.defineProperty;
|
|
|
4879
5878
|
watchContextMeter();
|
|
4880
5879
|
noteOccupancySession(sessionId ?? null);
|
|
4881
5880
|
}, [sessionId, rpc]);
|
|
4882
|
-
(0,
|
|
5881
|
+
(0, import_react22.useEffect)(() => {
|
|
4883
5882
|
if (!open) return;
|
|
4884
5883
|
const onDown = (e) => {
|
|
4885
5884
|
if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
|
|
@@ -5019,8 +6018,8 @@ var __defProp = Object.defineProperty;
|
|
|
5019
6018
|
pillStyle.boxShadow = "0 0 12px color-mix(in srgb, " + info.color + " 30%, transparent)";
|
|
5020
6019
|
pillStyle["--dsh-mem-pill-tint"] = info.color;
|
|
5021
6020
|
}
|
|
5022
|
-
return /* @__PURE__ */ (0,
|
|
5023
|
-
/* @__PURE__ */ (0,
|
|
6021
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { ref: wrapRef, style: { position: "relative", display: "inline-flex" }, children: [
|
|
6022
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
5024
6023
|
"button",
|
|
5025
6024
|
{
|
|
5026
6025
|
type: "button",
|
|
@@ -5033,11 +6032,11 @@ var __defProp = Object.defineProperty;
|
|
|
5033
6032
|
style: pillStyle,
|
|
5034
6033
|
children: [
|
|
5035
6034
|
"记忆 · ",
|
|
5036
|
-
/* @__PURE__ */ (0,
|
|
6035
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: faceLabel })
|
|
5037
6036
|
]
|
|
5038
6037
|
}
|
|
5039
6038
|
),
|
|
5040
|
-
open ? /* @__PURE__ */ (0,
|
|
6039
|
+
open ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5041
6040
|
"div",
|
|
5042
6041
|
{
|
|
5043
6042
|
ref: popRef,
|
|
@@ -5048,12 +6047,12 @@ var __defProp = Object.defineProperty;
|
|
|
5048
6047
|
transform: "translateX(calc(-50% + " + shiftX + "px))",
|
|
5049
6048
|
zIndex: 1e3
|
|
5050
6049
|
},
|
|
5051
|
-
children: /* @__PURE__ */ (0,
|
|
6050
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5052
6051
|
"div",
|
|
5053
6052
|
{
|
|
5054
6053
|
className: "dsh-mem-popover",
|
|
5055
6054
|
style: { position: "relative", padding: "10px 12px" },
|
|
5056
|
-
children: /* @__PURE__ */ (0,
|
|
6055
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
5057
6056
|
WingWheel,
|
|
5058
6057
|
{
|
|
5059
6058
|
mode: mode || "auto",
|
|
@@ -5077,245 +6076,6 @@ var __defProp = Object.defineProperty;
|
|
|
5077
6076
|
] });
|
|
5078
6077
|
}
|
|
5079
6078
|
|
|
5080
|
-
// client/src/longtask-fab.ts
|
|
5081
|
-
var FAB_ID = "dsh-mem-longtask-fab";
|
|
5082
|
-
var PANEL_ID = "dsh-mem-longtask-panel";
|
|
5083
|
-
var STYLE_ID = "dsh-mem-longtask-style";
|
|
5084
|
-
var PARASITE = "dsh-mem-parasite";
|
|
5085
|
-
var POS_KEY = "dsh.memory.longtask.pos";
|
|
5086
|
-
function initLongTaskFab(rpc, sessionIdOf) {
|
|
5087
|
-
if (document.getElementById(FAB_ID) !== null) return;
|
|
5088
|
-
const style = document.createElement("style");
|
|
5089
|
-
style.id = STYLE_ID;
|
|
5090
|
-
style.textContent = `
|
|
5091
|
-
#${FAB_ID} { position: fixed; z-index: 9999; width: 42px; height: 42px; border-radius: 50%;
|
|
5092
|
-
background: rgba(30,30,32,.72); backdrop-filter: blur(22px) saturate(180%);
|
|
5093
|
-
border: 1px solid rgba(255,255,255,.14); box-shadow: 0 8px 24px rgba(0,0,0,.4);
|
|
5094
|
-
color: #f5f5f7; font-size: 19px; line-height: 40px; text-align: center; cursor: grab;
|
|
5095
|
-
touch-action: none; user-select: none; display: none; transition: transform .18s; }
|
|
5096
|
-
#${FAB_ID}:hover { transform: scale(1.08); }
|
|
5097
|
-
#${FAB_ID}.lit { display: block; box-shadow: 0 0 0 5px rgba(10,132,255,.22), 0 8px 24px rgba(0,0,0,.4);
|
|
5098
|
-
animation: dsh-mem-lt-breathe 2.4s ease-in-out infinite; }
|
|
5099
|
-
@keyframes dsh-mem-lt-breathe { 0%,100% { box-shadow: 0 0 0 4px rgba(10,132,255,.16), 0 8px 24px rgba(0,0,0,.4); }
|
|
5100
|
-
50% { box-shadow: 0 0 0 8px rgba(10,132,255,.30), 0 8px 24px rgba(0,0,0,.4); } }
|
|
5101
|
-
#${PANEL_ID} { position: fixed; z-index: 9998; width: 336px; border-radius: 14px;
|
|
5102
|
-
background: rgba(28,28,30,.78); backdrop-filter: blur(26px) saturate(180%);
|
|
5103
|
-
border: 1px solid rgba(255,255,255,.12); box-shadow: 0 14px 44px rgba(0,0,0,.55);
|
|
5104
|
-
color: #f5f5f7; font-size: 12.5px; opacity: 0; pointer-events: none;
|
|
5105
|
-
transform: translateY(10px) scale(.92); transform-origin: 88% 100%;
|
|
5106
|
-
transition: opacity .22s cubic-bezier(.16,.8,.3,1.05), transform .22s cubic-bezier(.16,.8,.3,1.1); }
|
|
5107
|
-
#${PANEL_ID}.open { opacity: 1; pointer-events: auto; transform: none; }
|
|
5108
|
-
#${PANEL_ID} h4 { margin: 0; padding: 10px 14px 8px; font-size: 13px; cursor: grab;
|
|
5109
|
-
touch-action: none; user-select: none; border-bottom: 1px solid rgba(255,255,255,.09); }
|
|
5110
|
-
#${PANEL_ID} .row { display: flex; align-items: center; gap: 8px; padding: 7px 14px; }
|
|
5111
|
-
#${PANEL_ID} .muted { color: rgba(245,245,247,.55); }
|
|
5112
|
-
#${PANEL_ID} button { font: inherit; border-radius: 8px; border: 1px solid rgba(255,255,255,.18);
|
|
5113
|
-
background: rgba(10,132,255,.85); color: #fff; padding: 4px 10px; cursor: pointer; }
|
|
5114
|
-
#${PANEL_ID} button.ghost { background: transparent; }
|
|
5115
|
-
#${PANEL_ID} button:disabled { opacity: .45; cursor: default; }
|
|
5116
|
-
#${PANEL_ID} .bar { flex: 1; height: 6px; border-radius: 3px; background: rgba(255,255,255,.14); overflow: hidden; }
|
|
5117
|
-
#${PANEL_ID} .bar > i { display: block; height: 100%; background: #0a84ff; }
|
|
5118
|
-
#${PANEL_ID} .bar.hot > i { background: #ff9f0a; }
|
|
5119
|
-
#${PANEL_ID} .todos { max-height: 168px; overflow: auto; padding: 2px 14px 6px; color: rgba(245,245,247,.8); white-space: pre-wrap; }
|
|
5120
|
-
#${PANEL_ID} .hint { padding: 0 14px 8px; color: #ffd60a; display: none; }
|
|
5121
|
-
#${PANEL_ID} .hint.show { display: block; }
|
|
5122
|
-
`;
|
|
5123
|
-
document.head.appendChild(style);
|
|
5124
|
-
const fab = document.createElement("div");
|
|
5125
|
-
fab.id = FAB_ID;
|
|
5126
|
-
fab.className = PARASITE;
|
|
5127
|
-
fab.textContent = "⏳";
|
|
5128
|
-
fab.title = "长任务助手";
|
|
5129
|
-
const panel = document.createElement("div");
|
|
5130
|
-
panel.id = PANEL_ID;
|
|
5131
|
-
panel.className = PARASITE;
|
|
5132
|
-
panel.hidden = true;
|
|
5133
|
-
panel.innerHTML = `
|
|
5134
|
-
<h4>长任务助手</h4>
|
|
5135
|
-
<div class="row"><label style="flex:1">长任务模式</label><button data-toggle>关闭</button></div>
|
|
5136
|
-
<div class="row muted" data-why style="display:none"></div>
|
|
5137
|
-
<div class="row"><span style="width:86px">上下文占用</span><div class="bar"><i></i></div><span data-ctx class="muted">–</span></div>
|
|
5138
|
-
<div class="row"><span style="width:86px">任务漂移</span><div class="bar" data-driftbar><i></i></div><span data-drift class="muted">–</span></div>
|
|
5139
|
-
<div class="hint" data-hint>建议开启长任务模式:临近上下文压缩高风险区 / 任务内容已大幅漂移。</div>
|
|
5140
|
-
<div class="row"><button data-compress>压缩最近轮次入记忆</button><span class="muted" data-tail></span></div>
|
|
5141
|
-
<div class="todos" data-todos>尚无任务快照</div>
|
|
5142
|
-
`;
|
|
5143
|
-
document.body.append(fab, panel);
|
|
5144
|
-
let pos;
|
|
5145
|
-
try {
|
|
5146
|
-
pos = JSON.parse(localStorage.getItem(POS_KEY) ?? "null") ?? {
|
|
5147
|
-
x: window.innerWidth - 66,
|
|
5148
|
-
y: window.innerHeight - 140
|
|
5149
|
-
};
|
|
5150
|
-
} catch {
|
|
5151
|
-
pos = { x: window.innerWidth - 66, y: window.innerHeight - 140 };
|
|
5152
|
-
}
|
|
5153
|
-
const place = () => {
|
|
5154
|
-
pos.x = Math.max(4, Math.min(window.innerWidth - 46, pos.x));
|
|
5155
|
-
pos.y = Math.max(4, Math.min(window.innerHeight - 46, pos.y));
|
|
5156
|
-
fab.style.left = `${pos.x}px`;
|
|
5157
|
-
fab.style.top = `${pos.y}px`;
|
|
5158
|
-
};
|
|
5159
|
-
place();
|
|
5160
|
-
const makeDrag = (el, key) => {
|
|
5161
|
-
let sx = 0;
|
|
5162
|
-
let sy = 0;
|
|
5163
|
-
let bx = 0;
|
|
5164
|
-
let by = 0;
|
|
5165
|
-
let active = false;
|
|
5166
|
-
let dragged = false;
|
|
5167
|
-
el.addEventListener("pointerdown", (e) => {
|
|
5168
|
-
if (e.button !== 0 || e.target.closest?.("button, input")) return;
|
|
5169
|
-
active = true;
|
|
5170
|
-
dragged = false;
|
|
5171
|
-
sx = e.clientX;
|
|
5172
|
-
sy = e.clientY;
|
|
5173
|
-
bx = pos.x;
|
|
5174
|
-
by = pos.y;
|
|
5175
|
-
el.setPointerCapture(e.pointerId);
|
|
5176
|
-
});
|
|
5177
|
-
el.addEventListener("pointermove", (e) => {
|
|
5178
|
-
if (!active) return;
|
|
5179
|
-
const dx = e.clientX - sx;
|
|
5180
|
-
const dy = e.clientY - sy;
|
|
5181
|
-
if (!dragged && Math.abs(dx) + Math.abs(dy) > 5) dragged = true;
|
|
5182
|
-
if (dragged) {
|
|
5183
|
-
pos.x = bx + dx;
|
|
5184
|
-
pos.y = by + dy;
|
|
5185
|
-
place();
|
|
5186
|
-
}
|
|
5187
|
-
});
|
|
5188
|
-
el.addEventListener("pointerup", () => {
|
|
5189
|
-
active = false;
|
|
5190
|
-
if (dragged) {
|
|
5191
|
-
try {
|
|
5192
|
-
localStorage.setItem(POS_KEY, JSON.stringify(pos));
|
|
5193
|
-
} catch {
|
|
5194
|
-
}
|
|
5195
|
-
if (key === "fab") suppressClick = true;
|
|
5196
|
-
}
|
|
5197
|
-
});
|
|
5198
|
-
el.addEventListener("pointercancel", () => {
|
|
5199
|
-
active = false;
|
|
5200
|
-
});
|
|
5201
|
-
};
|
|
5202
|
-
let suppressClick = false;
|
|
5203
|
-
makeDrag(fab, "fab");
|
|
5204
|
-
let open = false;
|
|
5205
|
-
fab.addEventListener("click", () => {
|
|
5206
|
-
if (suppressClick) {
|
|
5207
|
-
suppressClick = false;
|
|
5208
|
-
return;
|
|
5209
|
-
}
|
|
5210
|
-
open = !open;
|
|
5211
|
-
if (open) {
|
|
5212
|
-
panel.hidden = false;
|
|
5213
|
-
const px = Math.max(8, Math.min(window.innerWidth - 344, pos.x - 300));
|
|
5214
|
-
const py = pos.y - 300 < 8 ? pos.y + 46 : Math.max(8, pos.y - 300);
|
|
5215
|
-
panel.style.left = `${px}px`;
|
|
5216
|
-
panel.style.top = `${py}px`;
|
|
5217
|
-
void panel.offsetHeight;
|
|
5218
|
-
panel.classList.add("open");
|
|
5219
|
-
} else {
|
|
5220
|
-
panel.classList.remove("open");
|
|
5221
|
-
setTimeout(() => {
|
|
5222
|
-
panel.hidden = true;
|
|
5223
|
-
}, 240);
|
|
5224
|
-
}
|
|
5225
|
-
});
|
|
5226
|
-
makeDrag(panel.querySelector("h4"), "panel");
|
|
5227
|
-
let last;
|
|
5228
|
-
const setToggle = (on, busy) => {
|
|
5229
|
-
const btn = panel.querySelector("[data-toggle]");
|
|
5230
|
-
btn.textContent = on ? "开启中" : "关闭";
|
|
5231
|
-
btn.disabled = busy;
|
|
5232
|
-
};
|
|
5233
|
-
const render = (h) => {
|
|
5234
|
-
last = h;
|
|
5235
|
-
fab.classList.toggle("lit", Boolean(h.enabled && h.suggest && !h.longTask));
|
|
5236
|
-
setToggle(h.longTask, false);
|
|
5237
|
-
const ctxBar = panel.querySelector(".row .bar > i");
|
|
5238
|
-
const ctxPctEl = panel.querySelector("[data-ctx]");
|
|
5239
|
-
const pct = h.contextPct;
|
|
5240
|
-
if (pct === null) {
|
|
5241
|
-
ctxBar.style.width = "0%";
|
|
5242
|
-
ctxPctEl.textContent = "–";
|
|
5243
|
-
} else {
|
|
5244
|
-
ctxBar.style.width = `${Math.min(100, pct)}%`;
|
|
5245
|
-
ctxBar.parentElement?.classList.toggle("hot", pct >= h.contextThresholdPct);
|
|
5246
|
-
ctxPctEl.textContent = `${pct}%`;
|
|
5247
|
-
}
|
|
5248
|
-
const driftBar = panel.querySelector("[data-driftbar] > i");
|
|
5249
|
-
const driftEl = panel.querySelector("[data-drift]");
|
|
5250
|
-
if (h.drift === null) {
|
|
5251
|
-
driftBar.style.width = "0%";
|
|
5252
|
-
driftEl.textContent = "–";
|
|
5253
|
-
} else {
|
|
5254
|
-
driftBar.style.width = `${Math.min(100, Math.round(h.drift * 100))}%`;
|
|
5255
|
-
driftEl.textContent = `${Math.round(h.drift * 100)}%`;
|
|
5256
|
-
}
|
|
5257
|
-
const hint = panel.querySelector("[data-hint]");
|
|
5258
|
-
hint.classList.toggle("show", Boolean(h.suggest && !h.longTask));
|
|
5259
|
-
const tail = panel.querySelector("[data-tail]");
|
|
5260
|
-
tail.textContent = h.pendingTailTurns === null ? "" : h.pendingTailTurns > 0 ? `待压缩 ${h.pendingTailTurns} 轮` : "已全部入忆";
|
|
5261
|
-
panel.querySelector("[data-compress]").disabled = !h.longTask;
|
|
5262
|
-
};
|
|
5263
|
-
const fetchTodos = async () => {
|
|
5264
|
-
return "";
|
|
5265
|
-
};
|
|
5266
|
-
void fetchTodos;
|
|
5267
|
-
const tick = async () => {
|
|
5268
|
-
const sid = sessionIdOf();
|
|
5269
|
-
if (!sid) {
|
|
5270
|
-
setTimeout(tick, 5e3);
|
|
5271
|
-
return;
|
|
5272
|
-
}
|
|
5273
|
-
try {
|
|
5274
|
-
const r = await rpc("dsh-memory/longtask-hint-get", { sessionId: sid });
|
|
5275
|
-
if (r?.sessionId === sid) {
|
|
5276
|
-
render(r);
|
|
5277
|
-
if (String(r.longTask) !== panel.dataset.lt) {
|
|
5278
|
-
panel.dataset.lt = String(r.longTask);
|
|
5279
|
-
setToggle(r.longTask, false);
|
|
5280
|
-
}
|
|
5281
|
-
const todos = panel.querySelector("[data-todos]");
|
|
5282
|
-
todos.textContent = r.todoCount > 0 ? `任务 ${r.todoCount} 条 · 漂移 ${r.drift === null ? "–" : `${Math.round((r.drift ?? 0) * 100)}%`}
|
|
5283
|
-
(完整清单见会话 todo 面板;开启长任务后清单自动驻留上下文)` : "尚无任务快照";
|
|
5284
|
-
}
|
|
5285
|
-
} catch {
|
|
5286
|
-
}
|
|
5287
|
-
setTimeout(tick, last?.suggest ? 2e3 : 5e3);
|
|
5288
|
-
};
|
|
5289
|
-
setTimeout(tick, 1500);
|
|
5290
|
-
panel.querySelector("[data-toggle]")?.addEventListener("click", async () => {
|
|
5291
|
-
const sid = sessionIdOf();
|
|
5292
|
-
if (!sid) return;
|
|
5293
|
-
const btn = panel.querySelector("[data-toggle]");
|
|
5294
|
-
const next = !(last?.longTask ?? false);
|
|
5295
|
-
setToggle(next, true);
|
|
5296
|
-
try {
|
|
5297
|
-
const r = await rpc("dsh-memory/session-mode-set", { sessionId: sid, longTask: next });
|
|
5298
|
-
if (last) last.longTask = Boolean(r.longTask ?? next);
|
|
5299
|
-
setToggle(Boolean(r.longTask ?? next), false);
|
|
5300
|
-
} catch {
|
|
5301
|
-
setToggle(!next, false);
|
|
5302
|
-
}
|
|
5303
|
-
});
|
|
5304
|
-
panel.querySelector("[data-compress]")?.addEventListener("click", async () => {
|
|
5305
|
-
const sid = sessionIdOf();
|
|
5306
|
-
if (!sid || !last?.longTask) return;
|
|
5307
|
-
const btn = panel.querySelector("[data-compress]");
|
|
5308
|
-
btn.disabled = true;
|
|
5309
|
-
try {
|
|
5310
|
-
const r = await rpc("dsh-memory/longtask-compress-tail", { sessionId: sid });
|
|
5311
|
-
const tail = panel.querySelector("[data-tail]");
|
|
5312
|
-
tail.textContent = `已入队 ${r.enqueued ?? 0} 条消息蒸馏`;
|
|
5313
|
-
} catch {
|
|
5314
|
-
}
|
|
5315
|
-
btn.disabled = false;
|
|
5316
|
-
});
|
|
5317
|
-
}
|
|
5318
|
-
|
|
5319
6079
|
// client/src/entry.tsx
|
|
5320
6080
|
var inject = ["slots"];
|
|
5321
6081
|
var SETTINGS_SEAT = "settings.section";
|
|
@@ -5323,12 +6083,6 @@ var __defProp = Object.defineProperty;
|
|
|
5323
6083
|
function apply(ctx) {
|
|
5324
6084
|
const rpc = makeRpc(ctx);
|
|
5325
6085
|
console.info("[dsh-prime-memory] client apply: slots 注入就绪,注册 UI 槽位");
|
|
5326
|
-
let latestSessionId;
|
|
5327
|
-
try {
|
|
5328
|
-
initLongTaskFab(rpc, () => latestSessionId);
|
|
5329
|
-
} catch (err) {
|
|
5330
|
-
console.warn("[dsh-prime-memory] 长任务浮动球挂载失败(不影响主面板):", err);
|
|
5331
|
-
}
|
|
5332
6086
|
const SETTINGS_SEAT2 = "settings.section";
|
|
5333
6087
|
let cardSeatLive = false;
|
|
5334
6088
|
try {
|
|
@@ -5362,7 +6116,6 @@ var __defProp = Object.defineProperty;
|
|
|
5362
6116
|
id: "dsh-memory-mode",
|
|
5363
6117
|
order: 100,
|
|
5364
6118
|
inject: (sessionId) => {
|
|
5365
|
-
latestSessionId = sessionId;
|
|
5366
6119
|
return { sessionId, rpc };
|
|
5367
6120
|
}
|
|
5368
6121
|
},
|