dsh-prime-memory 0.18.4 → 0.18.5

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/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 import_react17 = require("react");
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 btn = wrapRef.current.querySelector("button");
1649
- if (btn && btn.focus) btn.focus();
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) setRooms(r.value.rooms ?? []);
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
- rooms.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, alignItems: "center", marginBottom: 10 }, children: [
3519
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: S.muted, children: "Room 分类" }),
3520
- rooms.slice(0, 40).map((r) => {
3521
- const on = tagFilter === r.room;
3522
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
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: on ? "取消按该 Room 筛选" : "按该 Room 筛选记录",
3555
+ title: "清除 Room/孤儿筛选",
3527
3556
  onClick: () => {
3528
- const next = on ? "" : r.room;
3529
- setTagFilter(next);
3530
- const conds = { query: query.trim(), type: typeFilter, scene: sceneFilter, halls: hallFilter, tag: next, retired: retiredFilter };
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
- cursor: "pointer",
3536
- fontSize: 12,
3537
- padding: "2px 8px",
3538
- borderRadius: 999,
3539
- border: on ? "1px solid var(--dsh-mem-accent)" : "1px solid var(--dsh-mem-border)",
3540
- background: on ? "var(--dsh-mem-bg-inset)" : "transparent",
3541
- color: on ? "var(--dsh-mem-accent)" : "var(--dsh-mem-text-2)"
3542
- },
3543
- children: r.room + " · " + r.count
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
- r.room
3546
- );
3547
- }),
3548
- rooms.length > 40 ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: S.muted, children: "(仅显示前 40 / 共 " + rooms.length + ")" }) : null
3549
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: { ...S.muted, marginBottom: 10 }, children: "Room 分类:暂无(由反刍涌现的标签自动生成,无需手工建立)" }),
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,1075 @@ var __defProp = Object.defineProperty;
3774
3847
  ] });
3775
3848
  }
3776
3849
 
3777
- // client/src/tabs/ScenesTab.tsx
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 [expandedHalls, setExpandedHalls] = (0, import_react16.useState)(() => {
3901
+ try {
3902
+ const v = window.localStorage.getItem("dsh.memory.halls.open");
3903
+ return v ? new Set(JSON.parse(v)) : /* @__PURE__ */ new Set();
3904
+ } catch {
3905
+ return /* @__PURE__ */ new Set();
3906
+ }
3907
+ });
3908
+ const [filter, setFilter] = (0, import_react16.useState)("");
3909
+ const dragRef = (0, import_react16.useRef)(null);
3910
+ const [ghost, setGhost] = (0, import_react16.useState)(null);
3911
+ const onDragLabelDown = (ev, slug) => {
3912
+ if (!hiPriv || busy || ev.button !== 0) return;
3913
+ ev.currentTarget.setPointerCapture(ev.pointerId);
3914
+ dragRef.current = { from: slug, x: ev.clientX, y: ev.clientY, active: false };
3915
+ };
3916
+ const onDragLabelMove = (ev) => {
3917
+ const d = dragRef.current;
3918
+ if (!d) return;
3919
+ if (!d.active && Math.hypot(ev.clientX - d.x, ev.clientY - d.y) > 6) d.active = true;
3920
+ if (!d.active) return;
3921
+ setGhost({ x: ev.clientX, y: ev.clientY, text: d.from });
3922
+ const hall = document.elementFromPoint(ev.clientX, ev.clientY)?.closest?.("[data-hall]")?.getAttribute("data-hall") ?? null;
3923
+ setDragOverHall(hall);
3924
+ };
3925
+ const onDragLabelUp = (ev) => {
3926
+ const d = dragRef.current;
3927
+ dragRef.current = null;
3928
+ setGhost(null);
3929
+ if (!d) return;
3930
+ if (!d.active) return;
3931
+ const hall = document.elementFromPoint(ev.clientX, ev.clientY)?.closest?.("[data-hall]")?.getAttribute("data-hall");
3932
+ setDragOverHall(null);
3933
+ if (hall) beginReclass(d.from, hall);
3934
+ };
3935
+ const loadAll = (0, import_react16.useCallback)(() => {
3936
+ rpc("dsh-memory/rooms-get", {}).then((r) => {
3937
+ if (r && r.ok) {
3938
+ setRooms(r.value.rooms ?? []);
3939
+ setRegistry(r.value.registry ?? []);
3940
+ setOrphanCount(r.value.orphanCount ?? 0);
3941
+ }
3942
+ }).catch(() => void 0);
3943
+ }, [rpc]);
3944
+ const loadHiPriv = (0, import_react16.useCallback)(() => {
3945
+ rpc("dsh-memory/settings-get", {}).then((r) => {
3946
+ if (r && r.ok && r.value) setHiPriv(!!r.value.settings.memoryMutate);
3947
+ }).catch(() => {
3948
+ });
3949
+ }, [rpc]);
3950
+ (0, import_react16.useEffect)(() => {
3951
+ loadAll();
3952
+ loadHiPriv();
3953
+ }, [loadAll, loadHiPriv]);
3954
+ const toggleHiPriv = () => {
3955
+ const next = !hiPriv;
3956
+ if (next && !window.confirm("开启高权限模式:模型获得写入/删除记忆工具,并解锁 Hall 页的分类管理操作。确定开启?")) return;
3957
+ setHiPrivBusy(true);
3958
+ rpc("dsh-memory/settings-set", { memoryMutate: next }).then((r) => {
3959
+ if (r && r.ok) setHiPriv(next);
3960
+ else if (r) setError(r.error ? r.error.message : "切换高权限模式失败");
3961
+ setHiPrivBusy(false);
3962
+ loadHiPriv();
3963
+ }).catch((e) => {
3964
+ setHiPrivBusy(false);
3965
+ setError(String(e && e.message || e));
3966
+ });
3967
+ };
3968
+ const run = (fn) => {
3969
+ if (busy) return;
3970
+ setBusy(true);
3971
+ setError(null);
3972
+ setMsg(null);
3973
+ fn().finally(() => setBusy(false));
3974
+ };
3975
+ const register = () => run(async () => {
3976
+ const r = await rpc("dsh-memory/room-register", {
3977
+ slug: regSlug.trim(),
3978
+ label: regLabel.trim() || void 0,
3979
+ description: regDesc.trim() || void 0
3980
+ });
3981
+ if (r && r.ok) {
3982
+ setMsg(r.value?.notice ?? `已注册:${regSlug.trim()}`);
3983
+ setRegSlug("");
3984
+ setRegLabel("");
3985
+ setRegDesc("");
3986
+ loadAll();
3987
+ } else setError(r && r.error ? r.error.message : "注册失败");
3988
+ });
3989
+ const adopt = (slug) => run(async () => {
3990
+ const r = await rpc("dsh-memory/room-register", { slug, source: "grown" });
3991
+ if (r && r.ok) {
3992
+ setMsg(`已收编:${slug}(source=grown)`);
3993
+ loadAll();
3994
+ } else setError(r && r.error ? r.error.message : "收编失败");
3995
+ });
3996
+ const previewMerge = (override) => run(async () => {
3997
+ const fm = override ?? form;
3998
+ if (!fm) return;
3999
+ const to = fm.to.trim();
4000
+ if (!to) {
4001
+ setError("目标 slug 不能为空");
4002
+ return;
4003
+ }
4004
+ if (to === fm.from) {
4005
+ setError("目标与源相同");
4006
+ return;
4007
+ }
4008
+ const r = await rpc("dsh-memory/room-admin", { action: fm.action, from: fm.from, to });
4009
+ if (r && r.ok) {
4010
+ setPreview({ action: fm.action, from: fm.from, to, notice: String(r.value?.notice ?? "") });
4011
+ } else setError(r && r.error ? r.error.message : "预览失败");
4012
+ });
4013
+ const beginReclass = (from, hall) => {
4014
+ if (!busy) {
4015
+ setMsg(null);
4016
+ setError(null);
4017
+ }
4018
+ if (!hall) {
4019
+ setError("尚无 hall:先注册一个(如 dsh-plugin),再归类。");
4020
+ return;
4021
+ }
4022
+ if (majorOf(from) === hall) {
4023
+ if (registry.some((e) => e.slug === from)) {
4024
+ setMsg(`${from} 已在 ${hall} 下。`);
4025
+ return;
4026
+ }
4027
+ setPicking(null);
4028
+ void adopt(from);
4029
+ return;
4030
+ }
4031
+ const f = { action: "rename", from, to: `${hall}/${suffixOf(from)}` };
4032
+ setForm(f);
4033
+ setPreview(null);
4034
+ void previewMerge(f);
4035
+ };
4036
+ const execMerge = () => run(async () => {
4037
+ if (!preview) return;
4038
+ if (preview.action === "rename" && !registry.some((e) => e.slug === preview.from)) {
4039
+ const rr = await rpc("dsh-memory/room-register", { slug: preview.from, source: "grown" });
4040
+ if (!rr || !rr.ok) {
4041
+ setError(rr && rr.error ? rr.error.message : "收编源失败");
4042
+ return;
4043
+ }
4044
+ }
4045
+ const r = await rpc("dsh-memory/room-admin", { action: preview.action, from: preview.from, to: preview.to, dryRun: false });
4046
+ if (r && r.ok) {
4047
+ setMsg(String(r.value?.notice ?? "完成"));
4048
+ setPreview(null);
4049
+ setForm(null);
4050
+ loadAll();
4051
+ } else setError(r && r.error ? r.error.message : "执行失败");
4052
+ });
4053
+ const retire = (slug, active, withRecords = false) => run(async () => {
4054
+ if (!active) {
4055
+ const tip = withRecords ? `退役 Room:${slug} 并退场其下全部记忆?(记录为软删,可在记忆页「已退场」区逐条恢复)` : `退役 Room:${slug}?(存量记录的 tags 不动,仅词表不再推荐;可随时恢复)`;
4056
+ if (!window.confirm(tip)) return;
4057
+ }
4058
+ const r = await rpc("dsh-memory/room-admin", { action: "retire", slug, active, withRecords: withRecords || void 0 });
4059
+ if (r && r.ok) {
4060
+ setMsg(String(r.value?.notice ?? "完成"));
4061
+ loadAll();
4062
+ if (expandedRooms.has(slug)) {
4063
+ const rr = await rpc("dsh-memory/list-records", { tag: slug, limit: 10, retired: false });
4064
+ if (rr && rr.ok) setRoomRecords((m) => ({ ...m, [slug]: { items: rr.value.items, total: rr.value.total ?? null } }));
4065
+ }
4066
+ } else setError(r && r.error ? r.error.message : "操作失败");
4067
+ });
4068
+ const saveEditing = () => run(async () => {
4069
+ if (!editing) return;
4070
+ const r = await rpc("dsh-memory/room-admin", { action: "update", slug: editing.slug, label: editing.label, description: editing.description });
4071
+ if (r && r.ok) {
4072
+ setMsg(String(r.value?.notice ?? "已更新"));
4073
+ setEditing(null);
4074
+ loadAll();
4075
+ } else setError(r && r.error ? r.error.message : "更新失败");
4076
+ });
4077
+ const toggleRoomExpand = (slug) => run(async () => {
4078
+ if (expandedRooms.has(slug)) {
4079
+ const next = new Set(expandedRooms);
4080
+ next.delete(slug);
4081
+ setExpandedRooms(next);
4082
+ return;
4083
+ }
4084
+ const r = await rpc("dsh-memory/list-records", { tag: slug, limit: 10, retired: false });
4085
+ if (r && r.ok) {
4086
+ const next = new Set(expandedRooms);
4087
+ next.add(slug);
4088
+ setExpandedRooms(next);
4089
+ setRoomRecords((m) => ({ ...m, [slug]: { items: r.value.items, total: r.value.total ?? null } }));
4090
+ } else setError(r && r.error ? r.error.message : "加载失败");
4091
+ });
4092
+ const exportRecords = (slug) => run(async () => {
4093
+ const r = await rpc("dsh-memory/rooms-export", { kind: "records", tag: slug });
4094
+ if (!r.ok) {
4095
+ setError(r.error.message);
4096
+ return;
4097
+ }
4098
+ if (r.value.csv) downloadCsv(r.value.csv, `dsh-memory-room-${slug.replace(/\//g, "-")}-records.csv`);
4099
+ else setError("导出为空");
4100
+ });
4101
+ const groups = (0, import_react16.useMemo)(() => {
4102
+ const countOf = new Map(rooms.map((r) => [r.room, r.count]));
4103
+ const byHall = /* @__PURE__ */ new Map();
4104
+ for (const e of registry) {
4105
+ if (e.status !== "active") continue;
4106
+ const hall = majorOf(e.slug);
4107
+ const list = byHall.get(hall);
4108
+ if (list) list.push(e);
4109
+ else byHall.set(hall, [e]);
4110
+ }
4111
+ return [...byHall.entries()].map(([hall, entries]) => ({
4112
+ hall,
4113
+ label: registry.find((e) => e.slug === hall)?.label,
4114
+ entries: [...entries].sort((a, b) => a.slug.localeCompare(b.slug)),
4115
+ total: entries.reduce((s2, e) => s2 + (countOf.get(e.slug) ?? 0), 0)
4116
+ })).sort((a, b) => a.hall.localeCompare(b.hall));
4117
+ }, [registry, rooms]);
4118
+ const grownOnly = (0, import_react16.useMemo)(() => rooms.filter((r) => !registry.some((e) => e.slug === r.room)), [rooms, registry]);
4119
+ const persistHalls = (next) => {
4120
+ try {
4121
+ window.localStorage.setItem("dsh.memory.halls.open", JSON.stringify([...next]));
4122
+ } catch {
4123
+ }
4124
+ };
4125
+ const toggleHall = (hall) => setExpandedHalls((cur) => {
4126
+ const next = new Set(cur);
4127
+ if (next.has(hall)) next.delete(hall);
4128
+ else next.add(hall);
4129
+ persistHalls(next);
4130
+ return next;
4131
+ });
4132
+ const setAllHalls = (open) => {
4133
+ const next = open ? new Set(groups.map((g) => g.hall)) : /* @__PURE__ */ new Set();
4134
+ setExpandedHalls(next);
4135
+ persistHalls(next);
4136
+ };
4137
+ const normFilter = filter.trim().toLowerCase();
4138
+ const visibleGroups = (0, import_react16.useMemo)(() => {
4139
+ if (!normFilter) return groups;
4140
+ return groups.map((g) => {
4141
+ const hallHit = g.hall.toLowerCase().includes(normFilter) || (g.label ?? "").toLowerCase().includes(normFilter);
4142
+ const entries = hallHit ? g.entries : g.entries.filter(
4143
+ (e) => e.slug.toLowerCase().includes(normFilter) || (e.label ?? "").toLowerCase().includes(normFilter) || (e.aliases ?? []).some((a) => a.toLowerCase().includes(normFilter))
4144
+ );
4145
+ return { ...g, entries };
4146
+ }).filter((g) => g.entries.length > 0);
4147
+ }, [groups, normFilter]);
4148
+ const visibleGrown = (0, import_react16.useMemo)(
4149
+ () => normFilter ? grownOnly.filter((r) => r.room.toLowerCase().includes(normFilter)) : grownOnly,
4150
+ [grownOnly, normFilter]
4151
+ );
4152
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { children: [
4153
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { ...S.flexRow, marginBottom: 8 }, children: [
4154
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: "Hall = Room 大类 · 大类上限 200,小类「大类/小类」细分不占额度 · 与 Wing(域)筛选无关" }),
4155
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: S.grow }),
4156
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4157
+ "button",
4158
+ {
4159
+ type: "button",
4160
+ title: "导出分类数据 CSV(Room 词表+计数)",
4161
+ onClick: () => run(async () => {
4162
+ const r = await rpc("dsh-memory/rooms-export", { kind: "rooms" });
4163
+ if (!r.ok) {
4164
+ setError(r.error.message);
4165
+ return;
4166
+ }
4167
+ if (r.value.csv) downloadCsv(r.value.csv, "dsh-memory-rooms.csv");
4168
+ }),
4169
+ style: btn,
4170
+ children: "导出词表"
4171
+ }
4172
+ ),
4173
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
4174
+ "button",
4175
+ {
4176
+ type: "button",
4177
+ title: "导出无 Room 绑定的孤儿清单(含候选)",
4178
+ onClick: () => run(async () => {
4179
+ const r = await rpc("dsh-memory/rooms-export", { kind: "orphans" });
4180
+ if (!r.ok) {
4181
+ setError(r.error.message);
4182
+ return;
4183
+ }
4184
+ if (r.value.csv) downloadCsv(r.value.csv, "dsh-memory-room-orphans.csv");
4185
+ }),
4186
+ style: btn,
4187
+ children: [
4188
+ "导出孤儿 · ",
4189
+ orphanCount
4190
+ ]
4191
+ }
4192
+ ),
4193
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4194
+ "button",
4195
+ {
4196
+ type: "button",
4197
+ style: {
4198
+ ...btn,
4199
+ color: hiPriv ? "var(--dsh-mem-danger)" : void 0,
4200
+ border: hiPriv ? "1px solid var(--dsh-mem-danger)" : btn.border
4201
+ },
4202
+ disabled: hiPrivBusy,
4203
+ title: hiPriv ? "关闭高权限模式(收回模型写删工具与管理操作)" : "开启高权限模式以解锁分类管理操作",
4204
+ onClick: toggleHiPriv,
4205
+ children: hiPriv ? "高权限:开" : "高权限:关"
4206
+ }
4207
+ ),
4208
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", style: btn, onClick: loadAll, title: "重拉注册表与计数", children: "刷新" })
4209
+ ] }),
4210
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "center", marginBottom: 8, flexWrap: "wrap" }, children: [
4211
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4212
+ "input",
4213
+ {
4214
+ value: filter,
4215
+ onChange: (e) => setFilter(e.target.value),
4216
+ placeholder: "搜索 hall/room(slug、显示名、别名)",
4217
+ style: { ...S.input, width: 250, fontSize: 12 }
4218
+ }
4219
+ ),
4220
+ filter ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", style: btn, onClick: () => setFilter(""), children: "清除" }) : null,
4221
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: `匹配 ${visibleGroups.reduce((s2, g) => s2 + g.entries.length, 0)} room / ${visibleGroups.length} hall` }),
4222
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: S.grow }),
4223
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", style: btn, title: "展开全部 hall 的子 room", onClick: () => setAllHalls(true), children: "展开全部" }),
4224
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", style: btn, title: "收起全部 hall(只留 hall 头)", onClick: () => setAllHalls(false), children: "收起全部" })
4225
+ ] }),
4226
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, alignItems: "center", marginBottom: 10 }, children: [
4227
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4228
+ "input",
4229
+ {
4230
+ value: regSlug,
4231
+ onChange: (e) => setRegSlug(e.target.value),
4232
+ placeholder: "slug(如 dsh-plugin 或 dsh-plugin/merge)",
4233
+ disabled: !hiPriv || busy,
4234
+ style: { ...S.input, width: 210, fontSize: 12 }
4235
+ }
4236
+ ),
4237
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4238
+ "input",
4239
+ {
4240
+ value: regLabel,
4241
+ onChange: (e) => setRegLabel(e.target.value),
4242
+ placeholder: "名称(可中文)",
4243
+ disabled: !hiPriv || busy,
4244
+ style: { ...S.input, width: 110, fontSize: 12 }
4245
+ }
4246
+ ),
4247
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4248
+ "input",
4249
+ {
4250
+ value: regDesc,
4251
+ onChange: (e) => setRegDesc(e.target.value),
4252
+ placeholder: "归类说明(喂标注器)",
4253
+ disabled: !hiPriv || busy,
4254
+ style: { ...S.input, width: 170, fontSize: 12 }
4255
+ }
4256
+ ),
4257
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4258
+ "button",
4259
+ {
4260
+ type: "button",
4261
+ disabled: !hiPriv || busy || !regSlug.trim(),
4262
+ onClick: register,
4263
+ style: { ...btn, color: "var(--dsh-mem-accent)", borderColor: "var(--dsh-mem-accent)", opacity: !hiPriv || busy || !regSlug.trim() ? 0.5 : 1 },
4264
+ children: "注册"
4265
+ }
4266
+ ),
4267
+ !hiPriv && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: "管理操作需先开启高权限模式" })
4268
+ ] }),
4269
+ 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: [
4270
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { style: S.muted, children: [
4271
+ "编辑 ",
4272
+ editing.slug
4273
+ ] }),
4274
+ /* @__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 } }),
4275
+ /* @__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 } }),
4276
+ /* @__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: "保存" }),
4277
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", disabled: busy, onClick: () => setEditing(null), style: btn, children: "取消" })
4278
+ ] }) : null,
4279
+ 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: [
4280
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { style: S.muted, children: [
4281
+ form.action === "merge" ? "合并" : "归类/改名",
4282
+ " ",
4283
+ form.from,
4284
+ " →"
4285
+ ] }),
4286
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4287
+ "input",
4288
+ {
4289
+ value: form.to,
4290
+ onChange: (ev) => setForm({ ...form, to: ev.target.value }),
4291
+ placeholder: "目标 slug",
4292
+ disabled: busy,
4293
+ style: { ...S.input, width: 190, fontSize: 12 }
4294
+ }
4295
+ ),
4296
+ 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)(
4297
+ "button",
4298
+ {
4299
+ type: "button",
4300
+ title: `归入 ${gg.hall} 下(${gg.hall}/${suffixOf(form.from)})`,
4301
+ onClick: () => setForm({ ...form, to: `${gg.hall}/${suffixOf(form.from)}` }),
4302
+ style: { ...btn, borderRadius: 999, padding: "1px 8px" },
4303
+ children: gg.hall
4304
+ },
4305
+ gg.hall
4306
+ )) }) : null,
4307
+ /* @__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: "预览" }),
4308
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", disabled: busy, onClick: () => {
4309
+ setForm(null);
4310
+ setPreview(null);
4311
+ setPicking(null);
4312
+ }, style: btn, children: "取消" }),
4313
+ picking ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: "挑选中:点击目标 hall 头完成归类" }) : null,
4314
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: "与反刍并发会互相覆盖,请在反刍空闲时执行。" })
4315
+ ] }) : null,
4316
+ 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: [
4317
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { children: preview.notice }),
4318
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { display: "flex", gap: 6, marginTop: 4 }, children: [
4319
+ /* @__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: [
4320
+ "确认执行",
4321
+ preview.action === "merge" ? "合并" : "归类/改名"
4322
+ ] }),
4323
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", disabled: busy, onClick: () => setPreview(null), style: btn, children: "再改改" })
4324
+ ] })
4325
+ ] }) : null,
4326
+ msg ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: S.hint, children: msg }) : null,
4327
+ error ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: S.error, children: error }) : null,
4328
+ 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,
4329
+ visibleGroups.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: S.intro, children: normFilter ? "无匹配的 hall/room。" : "注册表为空。在上方注册 Hall/Room(如 dsh-plugin),或把自生长 slug「收编」进词表 —— 之后候选标注器会优先把相关记忆挂到这些 slug 上。" }) : visibleGroups.map((g) => {
4330
+ const hallOpen = !!normFilter || expandedHalls.has(g.hall);
4331
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { marginBottom: 10, border: "1px solid var(--dsh-mem-border)", borderRadius: 8, overflow: "hidden" }, children: [
4332
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
4333
+ "div",
4334
+ {
4335
+ "data-hall": g.hall,
4336
+ onDragOver: (ev) => {
4337
+ if (!hiPriv || busy) return;
4338
+ ev.preventDefault();
4339
+ ev.dataTransfer.dropEffect = "move";
4340
+ setDragOverHall(g.hall);
4341
+ },
4342
+ onDragLeave: () => setDragOverHall((cur) => cur === g.hall ? null : cur),
4343
+ onDrop: (ev) => {
4344
+ ev.preventDefault();
4345
+ setDragOverHall(null);
4346
+ if (!hiPriv || busy) return;
4347
+ const from = ev.dataTransfer.getData("text/dsh-room");
4348
+ if (from) beginReclass(from, g.hall);
4349
+ },
4350
+ onClick: () => {
4351
+ if (picking) {
4352
+ if (!busy) {
4353
+ const from = picking;
4354
+ setPicking(null);
4355
+ beginReclass(from, g.hall);
4356
+ }
4357
+ return;
4358
+ }
4359
+ toggleHall(g.hall);
4360
+ },
4361
+ style: {
4362
+ display: "flex",
4363
+ alignItems: "center",
4364
+ gap: 8,
4365
+ padding: "5px 10px",
4366
+ background: "var(--dsh-mem-bg-inset)",
4367
+ outline: dragOverHall === g.hall || picking !== null ? "2px dashed var(--dsh-mem-accent)" : void 0,
4368
+ outlineOffset: dragOverHall === g.hall || picking !== null ? "-2px" : void 0,
4369
+ cursor: "pointer"
4370
+ },
4371
+ title: picking ? "点击此 hall:把「" + picking + "」归入 " + g.hall + " 下(major/minor,预览后确认)" : hiPriv ? "拖入 room 归类到该 hall 下(major/minor;记录随迁,旧名进别名,预览后确认)" : void 0,
4372
+ children: [
4373
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4374
+ "button",
4375
+ {
4376
+ type: "button",
4377
+ title: hallOpen ? "收起子 room" : "展开子 room(默认折叠)",
4378
+ onClick: (ev) => {
4379
+ ev.stopPropagation();
4380
+ toggleHall(g.hall);
4381
+ },
4382
+ style: { ...btn, borderRadius: 999, padding: "1px 7px", flexShrink: 0 },
4383
+ children: hallOpen ? "▾" : "▸"
4384
+ }
4385
+ ),
4386
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { style: { fontSize: 12, fontWeight: 600 }, children: [
4387
+ g.hall,
4388
+ g.label ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: " (" + g.label + ")" }) : null
4389
+ ] }),
4390
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: `合计 ${g.total} 条 · ${g.entries.length} 个 room` }),
4391
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: S.grow }),
4392
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4393
+ "button",
4394
+ {
4395
+ type: "button",
4396
+ disabled: !hiPriv || busy,
4397
+ title: `预填注册表单为 ${g.hall}/…(注册该 hall 下的小类)`,
4398
+ onClick: (ev) => {
4399
+ ev.stopPropagation();
4400
+ setRegSlug(g.hall + "/");
4401
+ },
4402
+ style: btn,
4403
+ children: "+小类"
4404
+ }
4405
+ )
4406
+ ]
4407
+ }
4408
+ ),
4409
+ hallOpen ? g.entries.map((e) => {
4410
+ const standalone = e.slug === g.hall;
4411
+ const count = rooms.find((r) => r.room === e.slug)?.count ?? 0;
4412
+ const retired = e.status === "retired";
4413
+ 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: [
4414
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
4415
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { flex: 1, minWidth: 0, display: "flex", flexWrap: "wrap", gap: "2px 8px", alignItems: "baseline" }, children: [
4416
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4417
+ "button",
4418
+ {
4419
+ type: "button",
4420
+ title: expandedRooms.has(e.slug) ? "收起内容" : "展开查看该 Room 的记录内容(默认收起)",
4421
+ onClick: () => void toggleRoomExpand(e.slug),
4422
+ style: { ...btn, borderRadius: 999, padding: "1px 7px", flexShrink: 0 },
4423
+ children: expandedRooms.has(e.slug) ? "▾" : "▸"
4424
+ }
4425
+ ),
4426
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
4427
+ "span",
4428
+ {
4429
+ style: { fontSize: 12, cursor: hiPriv && !busy ? "grab" : "default", touchAction: "none" },
4430
+ onPointerDown: (ev) => onDragLabelDown(ev, e.slug),
4431
+ onPointerMove: onDragLabelMove,
4432
+ onPointerUp: onDragLabelUp,
4433
+ title: hiPriv && !busy ? "按住拖到某个 hall 头上归类(major/minor;记录随迁,旧名进别名)" : e.description ? `${e.slug} — ${e.description}` : e.slug,
4434
+ children: [
4435
+ standalone ? e.slug : "· " + e.slug.slice(g.hall.length + 1),
4436
+ e.source === "pre-registered" ? " ⭐" : ""
4437
+ ]
4438
+ }
4439
+ ),
4440
+ e.label ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: e.label }) : null,
4441
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: count + " 条" }),
4442
+ retired ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { ...S.muted, color: "var(--dsh-mem-danger)" }, children: "已退役" }) : null,
4443
+ (e.aliases ?? []).length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { ...S.muted, wordBreak: "break-all" }, children: "别名: " + (e.aliases ?? []).join(", ") }) : null
4444
+ ] }),
4445
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { display: "flex", gap: 6, flexShrink: 0, alignItems: "center" }, children: [
4446
+ !hiPriv ? null : /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
4447
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", disabled: busy, title: "编辑显示名/归类说明", onClick: () => {
4448
+ setEditing({ slug: e.slug, label: e.label ?? "", description: e.description ?? "" });
4449
+ setForm(null);
4450
+ setPreview(null);
4451
+ setPicking(null);
4452
+ }, style: btn, children: "编辑" }),
4453
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4454
+ "button",
4455
+ {
4456
+ type: "button",
4457
+ disabled: busy,
4458
+ title: picking === e.slug ? "再点一次取消挑选;然后点击目标 hall 头完成归类" : "归类到已有 hall 下:点击后选目标 hall(记录随迁,旧名进别名)",
4459
+ onClick: () => {
4460
+ if (picking === e.slug) {
4461
+ setPicking(null);
4462
+ return;
4463
+ }
4464
+ setForm({ action: "rename", from: e.slug, to: "" });
4465
+ setPreview(null);
4466
+ setPicking(e.slug);
4467
+ setMsg(null);
4468
+ setError(null);
4469
+ },
4470
+ style: { ...btn, ...picking === e.slug ? { color: "var(--dsh-mem-accent)", borderColor: "var(--dsh-mem-accent)" } : {} },
4471
+ children: picking === e.slug ? "选 hall…" : "归类"
4472
+ }
4473
+ ),
4474
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", disabled: busy, title: `改名(= merge 1:1 + 注册表改名)`, onClick: () => {
4475
+ setForm({ action: "rename", from: e.slug, to: "" });
4476
+ setPreview(null);
4477
+ }, style: btn, children: "改名" }),
4478
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", disabled: busy, title: "合并到另一个 Room(dryRun 预览→确认实跑)", onClick: () => {
4479
+ setForm({ action: "merge", from: e.slug, to: "" });
4480
+ setPreview(null);
4481
+ }, style: btn, children: "合并" }),
4482
+ /* @__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 ? "恢复" : "退役" }),
4483
+ !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
4484
+ ] }),
4485
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", disabled: busy, title: "导出该 Room 的记录清单 CSV", onClick: () => exportRecords(e.slug), style: btn, children: "导出" })
4486
+ ] })
4487
+ ] }),
4488
+ 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: [
4489
+ (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: [
4490
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "dsh-mem-tag dsh-mem-tag-" + it.type, children: TYPE_LABELS[it.type] || it.type }),
4491
+ " ",
4492
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: it.updatedAt ? fmtTime(it.updatedAt) : "" }),
4493
+ /* @__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 })
4494
+ ] }, it.id)),
4495
+ (roomRecords[e.slug]?.items.length ?? 0) === 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: S.muted, children: "该 Room 暂无记录。" }) : null,
4496
+ (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
4497
+ ] }) : null
4498
+ ] }, e.slug);
4499
+ }) : null
4500
+ ] }, g.hall);
4501
+ }),
4502
+ visibleGrown.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { marginTop: 4 }, children: [
4503
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { type: "button", onClick: () => setGrownOpen(!grownOpen), style: btn, title: grownOpen ? "收起未注册自生长 Room" : "展开未注册自生长 Room", children: [
4504
+ grownOpen ? "▾" : "▸",
4505
+ " 自生长未注册 · 共 ",
4506
+ visibleGrown.length
4507
+ ] }),
4508
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: "(只在 tags 里涌现,不在词表 —— 收编后标注器才会优先挂靠)" }),
4509
+ grownOpen ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, alignItems: "center", marginTop: 6 }, children: visibleGrown.map((r) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { style: { display: "inline-flex", alignItems: "center", gap: 4 }, children: [
4510
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4511
+ "span",
4512
+ {
4513
+ style: { ...btn, cursor: hiPriv && !busy ? "grab" : "default", borderRadius: 999, padding: "2px 8px", touchAction: "none" },
4514
+ onPointerDown: (ev) => onDragLabelDown(ev, r.room),
4515
+ onPointerMove: onDragLabelMove,
4516
+ onPointerUp: onDragLabelUp,
4517
+ title: hiPriv && !busy ? "自生长 slug(未注册)——按住拖到某个 hall 头上 = 收编并归为其子类" : "自生长 slug(未注册)",
4518
+ children: r.room + " · " + r.count
4519
+ }
4520
+ ),
4521
+ /* @__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: "收编" }),
4522
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4523
+ "button",
4524
+ {
4525
+ type: "button",
4526
+ disabled: !hiPriv || busy,
4527
+ title: "归类到已有 hall 下:点击后选目标 hall(收编+子类一步,记录随迁)",
4528
+ onClick: () => {
4529
+ if (picking === r.room) {
4530
+ setPicking(null);
4531
+ return;
4532
+ }
4533
+ setForm({ action: "rename", from: r.room, to: "" });
4534
+ setPreview(null);
4535
+ setPicking(r.room);
4536
+ setMsg(null);
4537
+ setError(null);
4538
+ },
4539
+ style: { ...btn, opacity: !hiPriv || busy ? 0.5 : 1, ...picking === r.room ? { color: "var(--dsh-mem-accent)", borderColor: "var(--dsh-mem-accent)" } : {} },
4540
+ children: picking === r.room ? "选 hall…" : "归"
4541
+ }
4542
+ )
4543
+ ] }, r.room)) }) : null
4544
+ ] }) : null
4545
+ ] });
4546
+ }
4547
+
4548
+ // client/src/tabs/RoomTab.tsx
4549
+ var import_react17 = require("react");
4550
+ var import_jsx_runtime17 = require("react/jsx-runtime");
4551
+ var btn2 = {
4552
+ cursor: "pointer",
4553
+ fontSize: 12,
4554
+ padding: "2px 8px",
4555
+ borderRadius: 6,
4556
+ border: "1px solid var(--dsh-mem-border)",
4557
+ background: "transparent",
4558
+ color: "var(--dsh-mem-text-2)"
4559
+ };
4560
+ function downloadCsv2(csv, name) {
4561
+ const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
4562
+ const url = URL.createObjectURL(blob);
4563
+ const a = document.createElement("a");
4564
+ a.href = url;
4565
+ a.download = name;
4566
+ a.click();
4567
+ URL.revokeObjectURL(url);
4568
+ }
4569
+ function RoomTab(props) {
4570
+ const rpc = props.rpc;
4571
+ const [registry, setRegistry] = (0, import_react17.useState)([]);
4572
+ const [counts, setCounts] = (0, import_react17.useState)([]);
4573
+ const [orphanCount, setOrphanCount] = (0, import_react17.useState)(0);
4574
+ const [busy, setBusy] = (0, import_react17.useState)(false);
4575
+ const [msg, setMsg] = (0, import_react17.useState)(null);
4576
+ const [error, setError] = (0, import_react17.useState)(null);
4577
+ const [hiPriv, setHiPriv] = (0, import_react17.useState)(false);
4578
+ const [hiPrivBusy, setHiPrivBusy] = (0, import_react17.useState)(false);
4579
+ const [reg, setReg] = (0, import_react17.useState)({ slug: "", label: "", description: "" });
4580
+ const [editing, setEditing] = (0, import_react17.useState)(null);
4581
+ const [renaming, setRenaming] = (0, import_react17.useState)(null);
4582
+ const [expanded, setExpanded] = (0, import_react17.useState)(/* @__PURE__ */ new Set());
4583
+ const [retiredFilter, setRetiredFilter] = (0, import_react17.useState)("");
4584
+ const [filter, setFilter] = (0, import_react17.useState)("");
4585
+ const [records, setRecords] = (0, import_react17.useState)({});
4586
+ const loadAll = (0, import_react17.useCallback)(() => {
4587
+ rpc("dsh-memory/rooms-get", {}).then((r) => {
4588
+ if (r && r.ok) {
4589
+ setCounts(r.value.rooms ?? []);
4590
+ setRegistry(r.value.registry ?? []);
4591
+ setOrphanCount(r.value.orphanCount ?? 0);
4592
+ }
4593
+ }).catch(() => void 0);
4594
+ }, [rpc]);
4595
+ const loadHiPriv = (0, import_react17.useCallback)(() => {
4596
+ rpc("dsh-memory/settings-get", {}).then((r) => {
4597
+ if (r && r.ok && r.value) setHiPriv(!!r.value.settings.memoryMutate);
4598
+ }).catch(() => {
4599
+ });
4600
+ }, [rpc]);
4601
+ (0, import_react17.useEffect)(() => {
4602
+ loadAll();
4603
+ loadHiPriv();
4604
+ }, [loadAll, loadHiPriv]);
4605
+ const toggleHiPriv = () => {
4606
+ const next = !hiPriv;
4607
+ if (next && !window.confirm("开启高权限模式:模型获得写入/删除记忆工具,并解锁 Room 管理操作。确定开启?")) return;
4608
+ rpc("dsh-memory/settings-set", { memoryMutate: next }).then((r) => {
4609
+ if (r && r.ok) setHiPriv(next);
4610
+ setHiPrivBusy(false);
4611
+ loadHiPriv();
4612
+ }).catch(() => setHiPrivBusy(false));
4613
+ };
4614
+ const run = (fn) => {
4615
+ if (busy) return;
4616
+ setBusy(true);
4617
+ setError(null);
4618
+ setMsg(null);
4619
+ fn().finally(() => setBusy(false));
4620
+ };
4621
+ const register = () => run(async () => {
4622
+ const r = await rpc("dsh-memory/room-register", { slug: reg.slug.trim(), label: reg.label.trim() || void 0, description: reg.description.trim() || void 0 });
4623
+ if (r && r.ok) {
4624
+ setMsg(r.value?.notice ?? `已注册:${reg.slug.trim()}`);
4625
+ setReg({ slug: "", label: "", description: "" });
4626
+ loadAll();
4627
+ } else setError(r && r.error ? r.error.message : "注册失败");
4628
+ });
4629
+ const adopt = (slug) => run(async () => {
4630
+ const r = await rpc("dsh-memory/room-register", { slug, source: "grown" });
4631
+ if (r && r.ok) {
4632
+ setMsg(`已收编:${slug}(source=grown)`);
4633
+ loadAll();
4634
+ } else setError(r && r.error ? r.error.message : "收编失败");
4635
+ });
4636
+ const previewRename = () => run(async () => {
4637
+ if (!renaming) return;
4638
+ const to = renaming.to.trim();
4639
+ if (!to) {
4640
+ setError("目标 slug 不能为空");
4641
+ return;
4642
+ }
4643
+ if (to === renaming.slug) {
4644
+ setError("目标与源相同");
4645
+ return;
4646
+ }
4647
+ if (!registry.some((e) => e.slug === renaming.slug)) {
4648
+ const rr = await rpc("dsh-memory/room-register", { slug: renaming.slug, source: "grown" });
4649
+ if (!rr || !rr.ok) {
4650
+ setError(rr && rr.error ? rr.error.message : "收编源失败");
4651
+ return;
4652
+ }
4653
+ loadAll();
4654
+ }
4655
+ const r = await rpc("dsh-memory/room-admin", { action: "rename", from: renaming.slug, to });
4656
+ if (r && r.ok) setMsg(String(r.value?.notice ?? ""));
4657
+ else setError(r && r.error ? r.error.message : "预览失败");
4658
+ });
4659
+ const execRename = () => run(async () => {
4660
+ if (!renaming) return;
4661
+ if (!registry.some((e) => e.slug === renaming.slug)) {
4662
+ await rpc("dsh-memory/room-register", { slug: renaming.slug, source: "grown" });
4663
+ }
4664
+ const r = await rpc("dsh-memory/room-admin", { action: "rename", from: renaming.slug, to: renaming.to.trim(), dryRun: false });
4665
+ if (r && r.ok) {
4666
+ setMsg(String(r.value?.notice ?? "完成"));
4667
+ setRenaming(null);
4668
+ loadAll();
4669
+ } else setError(r && r.error ? r.error.message : "执行失败");
4670
+ });
4671
+ const saveEditing = () => run(async () => {
4672
+ if (!editing) return;
4673
+ const r = await rpc("dsh-memory/room-admin", { action: "update", slug: editing.slug, label: editing.label, description: editing.description });
4674
+ if (r && r.ok) {
4675
+ setMsg(String(r.value?.notice ?? "已更新"));
4676
+ setEditing(null);
4677
+ loadAll();
4678
+ } else setError(r && r.error ? r.error.message : "更新失败");
4679
+ });
4680
+ const retire = (slug, active, withRecords = false) => run(async () => {
4681
+ if (!active) {
4682
+ const tip = withRecords ? `退役 Room:${slug} 并退场其下全部记忆?(记录为软删,可在记忆页「已退场」区逐条恢复)` : `退役 Room:${slug}?(存量记录的 tags 不动,仅词表不再推荐;可随时恢复)`;
4683
+ if (!window.confirm(tip)) return;
4684
+ }
4685
+ const r = await rpc("dsh-memory/room-admin", { action: "retire", slug, active, withRecords: withRecords || void 0 });
4686
+ if (r && r.ok) {
4687
+ setMsg(String(r.value?.notice ?? "完成"));
4688
+ loadAll();
4689
+ if (expanded.has(slug)) {
4690
+ const rr = await rpc("dsh-memory/list-records", { tag: slug, limit: 10, retired: false });
4691
+ if (rr && rr.ok) setRecords((m) => ({ ...m, [slug]: { items: rr.value.items, total: rr.value.total ?? null } }));
4692
+ }
4693
+ } else setError(r && r.error ? r.error.message : "操作失败");
4694
+ });
4695
+ const exportRecords = (slug) => run(async () => {
4696
+ const r = await rpc("dsh-memory/rooms-export", { kind: "records", tag: slug });
4697
+ if (!r.ok) {
4698
+ setError(r.error.message);
4699
+ return;
4700
+ }
4701
+ if (r.value.csv) {
4702
+ const blob = new Blob([r.value.csv], { type: "text/csv;charset=utf-8" });
4703
+ const url = URL.createObjectURL(blob);
4704
+ const a = document.createElement("a");
4705
+ a.href = url;
4706
+ a.download = `dsh-memory-room-${slug.replace(/\//g, "-")}-records.csv`;
4707
+ a.click();
4708
+ URL.revokeObjectURL(url);
4709
+ }
4710
+ });
4711
+ const toggleExpand = (slug) => run(async () => {
4712
+ if (expanded.has(slug)) {
4713
+ const next = new Set(expanded);
4714
+ next.delete(slug);
4715
+ setExpanded(next);
4716
+ return;
4717
+ }
4718
+ const r = await rpc("dsh-memory/list-records", { tag: slug, limit: 10, retired: false });
4719
+ if (r && r.ok) {
4720
+ const next = new Set(expanded);
4721
+ next.add(slug);
4722
+ setExpanded(next);
4723
+ setRecords((m) => ({ ...m, [slug]: { items: r.value.items, total: r.value.total ?? null } }));
4724
+ } else setError(r && r.error ? r.error.message : "加载失败");
4725
+ });
4726
+ const countOf = (slug) => counts.find((c) => c.room === slug)?.count ?? 0;
4727
+ const rows = [
4728
+ ...registry.map((e) => ({ slug: e.slug, count: countOf(e.slug), entry: e })),
4729
+ ...counts.filter((c) => !registry.some((e) => e.slug === c.room)).map((c) => ({ slug: c.room, count: c.count }))
4730
+ ].filter((row2) => {
4731
+ if (retiredFilter === "active") return row2.entry?.status !== "retired";
4732
+ if (retiredFilter === "retired") return row2.entry?.status === "retired";
4733
+ return true;
4734
+ });
4735
+ const normFilter = filter.trim().toLowerCase();
4736
+ const visibleRows = normFilter ? rows.filter(
4737
+ (row2) => row2.slug.toLowerCase().includes(normFilter) || (row2.entry?.label ?? "").toLowerCase().includes(normFilter) || (row2.entry?.aliases ?? []).some((a) => a.toLowerCase().includes(normFilter))
4738
+ ) : rows;
4739
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { children: [
4740
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { ...S.flexRow, marginBottom: 8, flexWrap: "wrap" }, children: [
4741
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: S.muted, children: "Room 级管理:存在(注册/收编/退役)· 名称(显示名/说明/改名)· 条目(展开浏览) · 归类到 hall 请到「Hall」页" }),
4742
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: S.grow }),
4743
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("button", { type: "button", style: btn2, title: "导出无 Room 绑定的孤儿清单(含候选)", onClick: () => run(async () => {
4744
+ const r = await rpc("dsh-memory/rooms-export", { kind: "orphans" });
4745
+ if (!r.ok) {
4746
+ setError(r.error.message);
4747
+ return;
4748
+ }
4749
+ if (r.value.csv) downloadCsv2(r.value.csv, "dsh-memory-room-orphans.csv");
4750
+ }), children: [
4751
+ "导出孤儿 · ",
4752
+ orphanCount
4753
+ ] }),
4754
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4755
+ "button",
4756
+ {
4757
+ type: "button",
4758
+ style: { ...btn2, color: hiPriv ? "var(--dsh-mem-danger)" : void 0, border: hiPriv ? "1px solid var(--dsh-mem-danger)" : btn2.border },
4759
+ disabled: hiPrivBusy,
4760
+ title: hiPriv ? "关闭高权限模式" : "开启高权限模式以解锁 Room 管理操作",
4761
+ onClick: toggleHiPriv,
4762
+ children: hiPriv ? "高权限:开" : "高权限:关"
4763
+ }
4764
+ ),
4765
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", style: btn2, onClick: loadAll, children: "刷新" })
4766
+ ] }),
4767
+ 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: [
4768
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { style: S.muted, children: [
4769
+ "编辑 ",
4770
+ editing.slug
4771
+ ] }),
4772
+ /* @__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 } }),
4773
+ /* @__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 } }),
4774
+ /* @__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: "保存" }),
4775
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", disabled: busy, onClick: () => setEditing(null), style: btn2, children: "取消" })
4776
+ ] }) : null,
4777
+ 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: [
4778
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { style: S.muted, children: [
4779
+ "改名 ",
4780
+ renaming.slug,
4781
+ " →"
4782
+ ] }),
4783
+ /* @__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 } }),
4784
+ /* @__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: "预览" }),
4785
+ /* @__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: "确认执行" }),
4786
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", disabled: busy, onClick: () => setRenaming(null), style: btn2, children: "取消" }),
4787
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: S.muted, children: "记录随迁;与反刍并发会互相覆盖,请在反刍空闲时执行。" })
4788
+ ] }) : null,
4789
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 8, flexWrap: "wrap" }, children: [
4790
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: S.muted, children: "退场筛选" }),
4791
+ [["", "全部"], ["active", "活跃"], ["retired", "已退役"]].map(([val, label]) => {
4792
+ const on = retiredFilter === val;
4793
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4794
+ "button",
4795
+ {
4796
+ type: "button",
4797
+ title: val === "" ? "全部 room" : val === "active" ? "只看词表未退役的 room" : "只看已退役的 room",
4798
+ onClick: () => setRetiredFilter(val),
4799
+ style: {
4800
+ cursor: "pointer",
4801
+ fontSize: 12,
4802
+ padding: "2px 10px",
4803
+ borderRadius: 999,
4804
+ border: on ? "1px solid var(--dsh-mem-accent)" : "1px solid var(--dsh-mem-border)",
4805
+ background: on ? "var(--dsh-mem-bg-inset)" : "transparent",
4806
+ color: on ? "var(--dsh-mem-accent)" : "var(--dsh-mem-text-2)"
4807
+ },
4808
+ children: label
4809
+ },
4810
+ val
4811
+ );
4812
+ }),
4813
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: S.grow }),
4814
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4815
+ "input",
4816
+ {
4817
+ value: filter,
4818
+ onChange: (e) => setFilter(e.target.value),
4819
+ placeholder: "搜索 room(slug、显示名、别名)",
4820
+ style: { ...S.input, width: 230, fontSize: 12 }
4821
+ }
4822
+ ),
4823
+ filter ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", style: btn2, onClick: () => setFilter(""), children: "清除" }) : null
4824
+ ] }),
4825
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, alignItems: "center", marginBottom: 10 }, children: [
4826
+ /* @__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 } }),
4827
+ /* @__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 } }),
4828
+ /* @__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 } }),
4829
+ /* @__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: "注册" }),
4830
+ !hiPriv && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: S.muted, children: "管理操作需先开启高权限模式" })
4831
+ ] }),
4832
+ msg ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: S.hint, children: msg }) : null,
4833
+ error ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: S.error, children: error }) : null,
4834
+ visibleRows.length === 0 ? normFilter ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { style: S.intro, children: "无匹配的 Room。" }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { style: S.intro, children: "暂无 Room:对话/反刍会从标签涌现,或在上方注册。" }) : visibleRows.map((row2) => {
4835
+ const e = row2.entry;
4836
+ const retired = e?.status === "retired";
4837
+ const grownOnly = !e;
4838
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { marginBottom: 6, border: "1px solid var(--dsh-mem-border)", borderRadius: 8 }, children: [
4839
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8, padding: "4px 10px" }, children: [
4840
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4841
+ "button",
4842
+ {
4843
+ type: "button",
4844
+ title: expanded.has(row2.slug) ? "收起记忆条目" : "展开查看该 Room 的记忆条目(默认收起)",
4845
+ onClick: () => void toggleExpand(row2.slug),
4846
+ style: { ...btn2, borderRadius: 999, padding: "1px 7px", flexShrink: 0 },
4847
+ children: expanded.has(row2.slug) ? "▾" : "▸"
4848
+ }
4849
+ ),
4850
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { flex: 1, minWidth: 0, display: "flex", flexWrap: "wrap", gap: "2px 8px", alignItems: "baseline" }, children: [
4851
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { style: { fontSize: 12, opacity: retired ? 0.6 : 1 }, children: [
4852
+ row2.slug,
4853
+ e?.source === "pre-registered" ? " ⭐" : ""
4854
+ ] }),
4855
+ e?.label ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: S.muted, children: e.label }) : null,
4856
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: S.muted, children: row2.count + " 条" }),
4857
+ grownOnly ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: S.muted, children: "(自生长未注册)" }) : null,
4858
+ retired ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: { ...S.muted, color: "var(--dsh-mem-danger)" }, children: "已退役" }) : null,
4859
+ (e?.aliases ?? []).length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: { ...S.muted, wordBreak: "break-all" }, children: "别名: " + (e?.aliases ?? []).join(", ") }) : null
4860
+ ] }),
4861
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { display: "flex", gap: 6, flexShrink: 0, alignItems: "center" }, children: [
4862
+ !hiPriv || grownOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
4863
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", disabled: busy, title: "编辑显示名/归类说明", onClick: () => {
4864
+ setEditing({ slug: row2.slug, label: e?.label ?? "", description: e?.description ?? "" });
4865
+ setRenaming(null);
4866
+ }, style: btn2, children: "编辑" }),
4867
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", disabled: busy, title: "改名(可两级 slug;记录随迁,旧名进别名)", onClick: () => {
4868
+ setRenaming({ slug: row2.slug, to: "" });
4869
+ setEditing(null);
4870
+ }, style: btn2, children: "改名" }),
4871
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4872
+ "button",
4873
+ {
4874
+ type: "button",
4875
+ disabled: busy,
4876
+ title: retired ? "恢复该 Room 到词表" : "退役该 Room(存量 tags 不动)",
4877
+ onClick: () => retire(row2.slug, retired),
4878
+ style: { ...btn2, ...retired ? { color: "var(--dsh-mem-accent)", borderColor: "var(--dsh-mem-accent)" } : { color: "var(--dsh-mem-danger)", borderColor: "var(--dsh-mem-danger)" } },
4879
+ children: retired ? "恢复" : "退役"
4880
+ }
4881
+ ),
4882
+ !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
4883
+ ] }),
4884
+ grownOnly ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
4885
+ /* @__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: "收编" }),
4886
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", disabled: !hiPriv || busy, title: "退场该 Room 下全部记忆(软删,可在「已退场」区恢复)", onClick: () => {
4887
+ setRenaming(null);
4888
+ retire(row2.slug, false, true);
4889
+ }, style: { ...btn2, color: "var(--dsh-mem-danger)", borderColor: "var(--dsh-mem-danger)", opacity: !hiPriv || busy ? 0.5 : 1 }, children: "退场" })
4890
+ ] }) : null,
4891
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", disabled: busy, title: "导出该 Room 的记录清单 CSV", onClick: () => exportRecords(row2.slug), style: btn2, children: "导出" })
4892
+ ] })
4893
+ ] }),
4894
+ 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: [
4895
+ (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: [
4896
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "dsh-mem-tag dsh-mem-tag-" + it.type, children: TYPE_LABELS[it.type] || it.type }),
4897
+ " ",
4898
+ it.retired ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: { ...S.muted, color: "var(--dsh-mem-danger)" }, children: "[已退场]" }) : null,
4899
+ " ",
4900
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: S.muted, children: it.updatedAt ? fmtTime(it.updatedAt) : "" }),
4901
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: { marginTop: 2, wordBreak: "break-all", color: "var(--dsh-mem-text-2)", opacity: it.retired ? 0.55 : 1 }, children: it.content.length > 140 ? it.content.slice(0, 140) + "…" : it.content })
4902
+ ] }, it.id)),
4903
+ (records[row2.slug]?.items.length ?? 0) === 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: S.muted, children: "该 Room 暂无记忆条目。" }) : null,
4904
+ (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
4905
+ ] }) : null
4906
+ ] }, row2.slug);
4907
+ })
4908
+ ] });
4909
+ }
4910
+
4911
+ // client/src/tabs/ScenesTab.tsx
4912
+ var import_react18 = require("react");
4913
+ var import_jsx_runtime18 = require("react/jsx-runtime");
3780
4914
  function SceneCard(props) {
3781
4915
  const s = props.s;
3782
- const [open, setOpen] = (0, import_react16.useState)(false);
3783
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "dsh-mem-card dsh-mem-card-hover", style: S.card, children: [
3784
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
4916
+ const [open, setOpen] = (0, import_react18.useState)(false);
4917
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "dsh-mem-card dsh-mem-card-hover", style: S.card, children: [
4918
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
3785
4919
  "div",
3786
4920
  {
3787
4921
  style: { ...S.sceneHead, cursor: "pointer", userSelect: "none" },
@@ -3789,23 +4923,23 @@ var __defProp = Object.defineProperty;
3789
4923
  setOpen(!open);
3790
4924
  },
3791
4925
  children: [
3792
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "dsh-mem-scene-chev", style: { transform: open ? "rotate(90deg)" : "none" }, children: "▸" }),
3793
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.sceneTitle, children: s.path }),
3794
- s.heat ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: "热度 " + s.heat }) : null,
3795
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: S.grow }),
3796
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: "更新 " + fmtTime(s.updated) })
4926
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "dsh-mem-scene-chev", style: { transform: open ? "rotate(90deg)" : "none" }, children: "▸" }),
4927
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { style: S.sceneTitle, children: s.path }),
4928
+ s.heat ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { style: S.muted, children: "热度 " + s.heat }) : null,
4929
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: S.grow }),
4930
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { style: S.muted, children: "更新 " + fmtTime(s.updated) })
3797
4931
  ]
3798
4932
  }
3799
4933
  ),
3800
- s.summary ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { ...S.muted, marginBottom: 6 }, children: s.summary }) : null,
3801
- open ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("pre", { style: S.pre, children: s.content || "(空)" }) : null
4934
+ s.summary ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { ...S.muted, marginBottom: 6 }, children: s.summary }) : null,
4935
+ open ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("pre", { style: S.pre, children: s.content || "(空)" }) : null
3802
4936
  ] });
3803
4937
  }
3804
4938
  function ScenesTab(props) {
3805
4939
  const rpc = props.rpc;
3806
- const [items, setItems] = (0, import_react16.useState)(null);
3807
- const [error, setError] = (0, import_react16.useState)(null);
3808
- const load = (0, import_react16.useCallback)(() => {
4940
+ const [items, setItems] = (0, import_react18.useState)(null);
4941
+ const [error, setError] = (0, import_react18.useState)(null);
4942
+ const load = (0, import_react18.useCallback)(() => {
3809
4943
  rpc("dsh-memory/scenes", {}).then((r) => {
3810
4944
  if (r && r.ok) {
3811
4945
  setItems(r.value.items);
@@ -3815,54 +4949,58 @@ var __defProp = Object.defineProperty;
3815
4949
  setError(String(e && e.message || e));
3816
4950
  });
3817
4951
  }, [rpc]);
3818
- (0, import_react16.useEffect)(() => {
4952
+ (0, import_react18.useEffect)(() => {
3819
4953
  load();
3820
4954
  }, [load]);
3821
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { children: [
3822
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { ...S.flexRow, marginBottom: 10 }, children: [
3823
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: items ? items.length + " 个场景块" : "加载中…" }),
3824
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: S.grow }),
3825
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(NButton, { onClick: load, children: "刷新" })
4955
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [
4956
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { ...S.flexRow, marginBottom: 10 }, children: [
4957
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { style: S.muted, children: items ? items.length + " 个场景块" : "加载中…" }),
4958
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: S.grow }),
4959
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(NButton, { onClick: load, children: "刷新" })
3826
4960
  ] }),
3827
- error ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: S.error, children: error }) : null,
3828
- items && items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: S.intro, children: "暂无场景块。累计 5 条新记忆后 L2 会自动整合出第一个场景。" }) : null,
4961
+ error ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: S.error, children: error }) : null,
4962
+ items && items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { style: S.intro, children: "暂无场景块。累计 5 条新记忆后 L2 会自动整合出第一个场景。" }) : null,
3829
4963
  (items || []).map((s) => {
3830
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(SceneCard, { s }, s.path);
4964
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SceneCard, { s }, s.path);
3831
4965
  })
3832
4966
  ] });
3833
4967
  }
3834
4968
 
3835
4969
  // client/src/panel.tsx
3836
- var import_jsx_runtime17 = require("react/jsx-runtime");
4970
+ var import_jsx_runtime19 = require("react/jsx-runtime");
3837
4971
  var TABS = [
3838
4972
  ["overview", "概览"],
3839
4973
  ["records", "记忆"],
3840
4974
  ["conflicts", "冲突"],
3841
4975
  ["scenes", "场景"],
4976
+ ["rooms", "Hall"],
4977
+ ["room", "Room"],
3842
4978
  ["persona", "画像"],
3843
4979
  ["cost", "成本"],
3844
4980
  ["log", "日志"]
3845
4981
  ];
3846
4982
  function MemoryPanel(props) {
3847
4983
  const rpc = props.rpc;
3848
- const [tab, setTab] = (0, import_react17.useState)("overview");
4984
+ const [tab, setTab] = (0, import_react19.useState)("overview");
3849
4985
  ensureThemeStyle();
3850
- (0, import_react17.useEffect)(() => {
4986
+ (0, import_react19.useEffect)(() => {
3851
4987
  watchSidebarIcon();
3852
4988
  }, []);
3853
4989
  let body;
3854
- if (tab === "overview") body = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(OverviewTab, { rpc });
3855
- else if (tab === "records") body = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(RecordsTab, { rpc });
3856
- else if (tab === "conflicts") body = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ConflictsTab, { rpc });
3857
- else if (tab === "scenes") body = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ScenesTab, { rpc });
3858
- else if (tab === "persona") body = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(PersonaTab, { rpc });
3859
- else if (tab === "cost") body = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(CostTab, { rpc });
3860
- else body = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(LogTab, { rpc });
3861
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "dsh-mem-root", style: S.section, children: [
3862
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("h2", { style: S.heading, children: "记忆 (Memory)" }),
3863
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { style: S.intro, children: "L0~L3 分层蒸馏记忆:浏览被记住的内容,控制记忆模式开关。" }),
3864
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: S.tabbar, children: TABS.map((t) => {
3865
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4990
+ if (tab === "overview") body = /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(OverviewTab, { rpc });
4991
+ else if (tab === "records") body = /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(RecordsTab, { rpc });
4992
+ else if (tab === "conflicts") body = /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ConflictsTab, { rpc });
4993
+ else if (tab === "scenes") body = /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ScenesTab, { rpc });
4994
+ else if (tab === "rooms") body = /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(RoomsTab, { rpc });
4995
+ else if (tab === "room") body = /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(RoomTab, { rpc });
4996
+ else if (tab === "persona") body = /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(PersonaTab, { rpc });
4997
+ else if (tab === "cost") body = /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(CostTab, { rpc });
4998
+ else body = /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(LogTab, { rpc });
4999
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "dsh-mem-root", style: S.section, children: [
5000
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h2", { style: S.heading, children: "记忆 (Memory)" }),
5001
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { style: S.intro, children: "L0~L3 分层蒸馏记忆:浏览被记住的内容,控制记忆模式开关。" }),
5002
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { style: S.tabbar, children: TABS.map((t) => {
5003
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
3866
5004
  "button",
3867
5005
  {
3868
5006
  className: tab === t[0] ? "dsh-mem-tab dsh-mem-tab-on" : "dsh-mem-tab",
@@ -3879,7 +5017,7 @@ var __defProp = Object.defineProperty;
3879
5017
  }
3880
5018
 
3881
5019
  // client/src/pill/MemoryModePill.tsx
3882
- var import_react20 = require("react");
5020
+ var import_react22 = require("react");
3883
5021
 
3884
5022
  // src/util/context-occupancy.ts
3885
5023
  var CONTEXT_METER_CIRCUMFERENCE = 34.55751918948772;
@@ -4195,23 +5333,23 @@ var __defProp = Object.defineProperty;
4195
5333
  }
4196
5334
 
4197
5335
  // client/src/pill/WingWheel.tsx
4198
- var import_react19 = require("react");
5336
+ var import_react21 = require("react");
4199
5337
 
4200
5338
  // client/src/pill/SessionInfoArea.tsx
4201
- var import_react18 = require("react");
4202
- var import_jsx_runtime18 = require("react/jsx-runtime");
5339
+ var import_react20 = require("react");
5340
+ var import_jsx_runtime20 = require("react/jsx-runtime");
4203
5341
  function sinfoCell(val, label, title) {
4204
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { title: title || void 0, children: [
4205
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dsh-mem-sinfo-val", children: val }),
4206
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dsh-mem-sinfo-label", children: label })
5342
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { title: title || void 0, children: [
5343
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "dsh-mem-sinfo-val", children: val }),
5344
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "dsh-mem-sinfo-label", children: label })
4207
5345
  ] });
4208
5346
  }
4209
5347
  function SessionInfoArea(props) {
4210
5348
  const rpc = props.rpc;
4211
5349
  const sessionId = props.sessionId;
4212
- const [stats, setStats] = (0, import_react18.useState)(void 0);
4213
- const busyRef = (0, import_react18.useRef)(false);
4214
- (0, import_react18.useEffect)(() => {
5350
+ const [stats, setStats] = (0, import_react20.useState)(void 0);
5351
+ const busyRef = (0, import_react20.useRef)(false);
5352
+ (0, import_react20.useEffect)(() => {
4215
5353
  if (!rpc || !sessionId) return void 0;
4216
5354
  let alive = true;
4217
5355
  let timer = null;
@@ -4244,7 +5382,7 @@ var __defProp = Object.defineProperty;
4244
5382
  }, [rpc, sessionId]);
4245
5383
  if (stats === null) return null;
4246
5384
  if (stats === void 0) {
4247
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dsh-mem-sinfo", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "dsh-mem-sinfo-grid", children: [
5385
+ 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
5386
  sinfoCell("…", "召回命中"),
4249
5387
  sinfoCell("…", "攒批进度"),
4250
5388
  sinfoCell("…", "本会话记忆"),
@@ -4293,21 +5431,21 @@ var __defProp = Object.defineProperty;
4293
5431
  else if (stats.retrieval === "none") note = "检索不可用(FTS 与向量均失效)";
4294
5432
  }
4295
5433
  const ago = fmtAgo(gl.lastExtractAt);
4296
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "dsh-mem-sinfo", children: [
4297
- warn ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dsh-mem-sinfo-warn", children: warn }) : null,
4298
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "dsh-mem-sinfo-grid", children: [
5434
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "dsh-mem-sinfo", children: [
5435
+ warn ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "dsh-mem-sinfo-warn", children: warn }) : null,
5436
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "dsh-mem-sinfo-grid", children: [
4299
5437
  sinfoCell(rcVal, rcLabel, rcTitle),
4300
5438
  sinfoCell(dVal, dLabel, dTitle),
4301
5439
  sinfoCell(String(di.producedRecords || 0), "本会话记忆", pTitle),
4302
5440
  sinfoCell(stats.l0Count != null ? String(stats.l0Count) : "…", "会话消息")
4303
5441
  ] }),
4304
- note ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dsh-mem-sinfo-note", children: note }) : null,
4305
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "dsh-mem-sinfo-sum", children: "待蒸馏 " + (gl.pendingTotal || 0) + " · 上次蒸馏 " + (ago || "尚未蒸馏") })
5442
+ note ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "dsh-mem-sinfo-note", children: note }) : null,
5443
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "dsh-mem-sinfo-sum", children: "待蒸馏 " + (gl.pendingTotal || 0) + " · 上次蒸馏 " + (ago || "尚未蒸馏") })
4306
5444
  ] });
4307
5445
  }
4308
5446
 
4309
5447
  // client/src/pill/WingWheel.tsx
4310
- var import_jsx_runtime19 = require("react/jsx-runtime");
5448
+ var import_jsx_runtime21 = require("react/jsx-runtime");
4311
5449
  var SIZE = 196;
4312
5450
  var CENTER = SIZE / 2;
4313
5451
  var R = 64;
@@ -4350,17 +5488,17 @@ var __defProp = Object.defineProperty;
4350
5488
  function WingWheel(props) {
4351
5489
  ensureThemeStyle();
4352
5490
  const isOff = props.mode === "off";
4353
- const [overview, setOverview] = (0, import_react19.useState)(null);
4354
- const [backfillBusy, setBackfillBusy] = (0, import_react19.useState)(false);
4355
- const [localError, setLocalError] = (0, import_react19.useState)(null);
4356
- const timersRef = (0, import_react19.useRef)([]);
4357
- (0, import_react19.useEffect)(
5491
+ const [overview, setOverview] = (0, import_react21.useState)(null);
5492
+ const [backfillBusy, setBackfillBusy] = (0, import_react21.useState)(false);
5493
+ const [localError, setLocalError] = (0, import_react21.useState)(null);
5494
+ const timersRef = (0, import_react21.useRef)([]);
5495
+ (0, import_react21.useEffect)(
4358
5496
  () => () => {
4359
5497
  for (const t of timersRef.current) window.clearTimeout(t);
4360
5498
  },
4361
5499
  []
4362
5500
  );
4363
- (0, import_react19.useEffect)(() => {
5501
+ (0, import_react21.useEffect)(() => {
4364
5502
  let alive = true;
4365
5503
  props.rpc("dsh-memory/wing-overview", {}).then((r) => {
4366
5504
  if (!alive) return;
@@ -4406,28 +5544,28 @@ var __defProp = Object.defineProperty;
4406
5544
  finish();
4407
5545
  });
4408
5546
  };
4409
- const boxRef = (0, import_react19.useRef)(null);
4410
- const startRef = (0, import_react19.useRef)(null);
4411
- const movedRef = (0, import_react19.useRef)(false);
4412
- const downIndexRef = (0, import_react19.useRef)(null);
4413
- const dragTargetRef = (0, import_react19.useRef)(null);
4414
- const [drag, setDrag] = (0, import_react19.useState)(null);
5547
+ const boxRef = (0, import_react21.useRef)(null);
5548
+ const startRef = (0, import_react21.useRef)(null);
5549
+ const movedRef = (0, import_react21.useRef)(false);
5550
+ const downIndexRef = (0, import_react21.useRef)(null);
5551
+ const dragTargetRef = (0, import_react21.useRef)(null);
5552
+ const [drag, setDrag] = (0, import_react21.useState)(null);
4415
5553
  const lockedIndex = (() => {
4416
5554
  if (props.halls.length !== 1 || !overview) return null;
4417
5555
  const i = overview.corners.findIndex((c) => c.id === props.halls[0]);
4418
5556
  return i >= 0 ? i : null;
4419
5557
  })();
4420
5558
  const activeCorner = drag ? drag.index : lockedIndex;
4421
- const blockPosRef = (0, import_react19.useRef)(
5559
+ const blockPosRef = (0, import_react21.useRef)(
4422
5560
  activeCorner != null ? cornerXY(activeCorner) : { x: CENTER, y: CENTER }
4423
5561
  );
4424
- const blockTargetRef = (0, import_react19.useRef)(blockPosRef.current);
4425
- const [blockPos, setBlockPos] = (0, import_react19.useState)(blockPosRef.current);
4426
- (0, import_react19.useEffect)(() => {
5562
+ const blockTargetRef = (0, import_react21.useRef)(blockPosRef.current);
5563
+ const [blockPos, setBlockPos] = (0, import_react21.useState)(blockPosRef.current);
5564
+ (0, import_react21.useEffect)(() => {
4427
5565
  blockTargetRef.current = activeCorner != null ? cornerXY(activeCorner) : { x: CENTER, y: CENTER };
4428
5566
  }, [activeCorner]);
4429
- const reducedMotionRef = (0, import_react19.useRef)(false);
4430
- (0, import_react19.useEffect)(() => {
5567
+ const reducedMotionRef = (0, import_react21.useRef)(false);
5568
+ (0, import_react21.useEffect)(() => {
4431
5569
  const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
4432
5570
  const apply2 = () => {
4433
5571
  reducedMotionRef.current = mq.matches;
@@ -4436,7 +5574,7 @@ var __defProp = Object.defineProperty;
4436
5574
  mq.addEventListener("change", apply2);
4437
5575
  return () => mq.removeEventListener("change", apply2);
4438
5576
  }, []);
4439
- (0, import_react19.useEffect)(() => {
5577
+ (0, import_react21.useEffect)(() => {
4440
5578
  let raf = 0;
4441
5579
  const loop = () => {
4442
5580
  const cur = blockPosRef.current;
@@ -4525,8 +5663,8 @@ var __defProp = Object.defineProperty;
4525
5663
  }).join(" ");
4526
5664
  const grayStyle = isOff ? { opacity: 0.45, pointerEvents: "none" } : {};
4527
5665
  const boundariesDisabled = props.halls.length === 0 || isOff;
4528
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { width: SIZE }, children: [
4529
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5666
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { style: { width: SIZE }, children: [
5667
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
4530
5668
  "div",
4531
5669
  {
4532
5670
  ref: boxRef,
@@ -4536,7 +5674,7 @@ var __defProp = Object.defineProperty;
4536
5674
  onPointerUp: onBoxUp,
4537
5675
  onPointerCancel: onBoxUp,
4538
5676
  children: [
4539
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5677
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
4540
5678
  "svg",
4541
5679
  {
4542
5680
  width: SIZE,
@@ -4544,7 +5682,7 @@ var __defProp = Object.defineProperty;
4544
5682
  style: { position: "absolute", inset: 0, pointerEvents: "none" },
4545
5683
  "aria-hidden": "true",
4546
5684
  children: [
4547
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5685
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4548
5686
  "polygon",
4549
5687
  {
4550
5688
  points: polyPoints,
@@ -4555,7 +5693,7 @@ var __defProp = Object.defineProperty;
4555
5693
  opacity: 0.45
4556
5694
  }
4557
5695
  ),
4558
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5696
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4559
5697
  "polygon",
4560
5698
  {
4561
5699
  points: polyPoints,
@@ -4566,7 +5704,7 @@ var __defProp = Object.defineProperty;
4566
5704
  ),
4567
5705
  [0, 1, 2, 3, 4, 5, 6, 7].map((i) => {
4568
5706
  const p = cornerPos(i);
4569
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5707
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4570
5708
  "line",
4571
5709
  {
4572
5710
  x1: CENTER,
@@ -4582,7 +5720,7 @@ var __defProp = Object.defineProperty;
4582
5720
  ]
4583
5721
  }
4584
5722
  ),
4585
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5723
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4586
5724
  "div",
4587
5725
  {
4588
5726
  "aria-hidden": "true",
@@ -4601,7 +5739,7 @@ var __defProp = Object.defineProperty;
4601
5739
  }
4602
5740
  }
4603
5741
  ),
4604
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5742
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4605
5743
  "button",
4606
5744
  {
4607
5745
  type: "button",
@@ -4635,7 +5773,7 @@ var __defProp = Object.defineProperty;
4635
5773
  const dim = activeCorner != null && !active;
4636
5774
  const empty = count === 0;
4637
5775
  const p = cornerPos(i);
4638
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5776
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
4639
5777
  "div",
4640
5778
  {
4641
5779
  title: id ? `「${label}」${count ?? 0} 条
@@ -4664,8 +5802,8 @@ var __defProp = Object.defineProperty;
4664
5802
  zIndex: 2
4665
5803
  },
4666
5804
  children: [
4667
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { children: label }),
4668
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { style: { fontSize: 8, opacity: 0.75, fontVariantNumeric: "tabular-nums" }, children: count === null ? " " : empty ? "空角" : `${count} 条` })
5805
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: label }),
5806
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { style: { fontSize: 8, opacity: 0.75, fontVariantNumeric: "tabular-nums" }, children: count === null ? " " : empty ? "空角" : `${count} 条` })
4669
5807
  ]
4670
5808
  },
4671
5809
  id ?? i
@@ -4674,7 +5812,7 @@ var __defProp = Object.defineProperty;
4674
5812
  ]
4675
5813
  }
4676
5814
  ),
4677
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5815
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
4678
5816
  "div",
4679
5817
  {
4680
5818
  title: props.halls.length === 0 ? "选定某个角(单 Wing)后,这两项才生效" : "锁定 Wing 时两类无角记忆是否参与召回",
@@ -4688,9 +5826,9 @@ var __defProp = Object.defineProperty;
4688
5826
  pointerEvents: boundariesDisabled ? "none" : void 0
4689
5827
  },
4690
5828
  children: [
4691
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { style: { fontSize: 11, color: "var(--dsh-mem-text-3)" }, children: overview ? `另有 ${overview.unlabeled} 条未打标` : "未打标" }),
4692
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: [
4693
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5829
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { style: { fontSize: 11, color: "var(--dsh-mem-text-3)" }, children: overview ? `另有 ${overview.unlabeled} 条未打标` : "未打标" }),
5830
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: [
5831
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4694
5832
  Segmented,
4695
5833
  {
4696
5834
  value: props.hallIncludeUnlabeled ? "in" : "ex",
@@ -4701,7 +5839,7 @@ var __defProp = Object.defineProperty;
4701
5839
  onChange: (key) => props.onCommitWingBoundaries({ includeUnlabeled: key === "in" })
4702
5840
  }
4703
5841
  ),
4704
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5842
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4705
5843
  Segmented,
4706
5844
  {
4707
5845
  value: props.hallIncludeGeneral ? "in" : "ex",
@@ -4716,7 +5854,7 @@ var __defProp = Object.defineProperty;
4716
5854
  ]
4717
5855
  }
4718
5856
  ),
4719
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Row, { label: "会话", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5857
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Row, { label: "会话", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4720
5858
  Segmented,
4721
5859
  {
4722
5860
  value: isOff ? "off" : "on",
@@ -4731,7 +5869,7 @@ var __defProp = Object.defineProperty;
4731
5869
  onChange: (key) => props.onCommit(key === "off" ? "off" : "auto")
4732
5870
  }
4733
5871
  ) }),
4734
- props.recall !== void 0 && props.onCommitRecall ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Row, { label: "注入", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5872
+ props.recall !== void 0 && props.onCommitRecall ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Row, { label: "注入", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4735
5873
  Segmented,
4736
5874
  {
4737
5875
  value: props.recall === null ? "follow" : props.recall ? "on" : "off",
@@ -4744,7 +5882,7 @@ var __defProp = Object.defineProperty;
4744
5882
  onChange: (key) => props.onCommitRecall(key === "on" ? true : key === "off" ? false : null)
4745
5883
  }
4746
5884
  ) }) : null,
4747
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5885
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
4748
5886
  "div",
4749
5887
  {
4750
5888
  style: {
@@ -4755,8 +5893,8 @@ var __defProp = Object.defineProperty;
4755
5893
  marginTop: 10
4756
5894
  },
4757
5895
  children: [
4758
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { style: { fontSize: 11, color: "var(--dsh-mem-text-3)" }, children: overview ? `未打标 ${overview.unlabeled} 条` : "未打标计数加载中" }),
4759
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5896
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { style: { fontSize: 11, color: "var(--dsh-mem-text-3)" }, children: overview ? `未打标 ${overview.unlabeled} 条` : "未打标计数加载中" }),
5897
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4760
5898
  ActionButton,
4761
5899
  {
4762
5900
  label: backfillBusy ? "回填中…" : "回填",
@@ -4768,12 +5906,12 @@ var __defProp = Object.defineProperty;
4768
5906
  ]
4769
5907
  }
4770
5908
  ),
4771
- props.error || localError ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { style: { fontSize: 11, color: "var(--dsh-mem-danger)", marginTop: 8 }, children: props.error || localError }) : null,
4772
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(SessionInfoArea, { rpc: props.rpc, sessionId: props.sessionId })
5909
+ 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,
5910
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SessionInfoArea, { rpc: props.rpc, sessionId: props.sessionId })
4773
5911
  ] });
4774
5912
  }
4775
5913
  function Row(props) {
4776
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5914
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
4777
5915
  "div",
4778
5916
  {
4779
5917
  style: {
@@ -4783,7 +5921,7 @@ var __defProp = Object.defineProperty;
4783
5921
  marginTop: 10
4784
5922
  },
4785
5923
  children: [
4786
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { style: { fontSize: 12, color: "var(--dsh-mem-text-3)" }, children: props.label }),
5924
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { style: { fontSize: 12, color: "var(--dsh-mem-text-3)" }, children: props.label }),
4787
5925
  props.children
4788
5926
  ]
4789
5927
  }
@@ -4791,9 +5929,9 @@ var __defProp = Object.defineProperty;
4791
5929
  }
4792
5930
  var LABEL_FALLBACK = ["工作", "人际", "学习", "创作娱乐", "居家", "健康", "财务", "出行"];
4793
5931
  function useViewportClamp(popRef) {
4794
- const shiftRef = (0, import_react19.useRef)(0);
4795
- const [shiftX, setShiftX] = (0, import_react19.useState)(0);
4796
- (0, import_react19.useEffect)(() => {
5932
+ const shiftRef = (0, import_react21.useRef)(0);
5933
+ const [shiftX, setShiftX] = (0, import_react21.useState)(0);
5934
+ (0, import_react21.useEffect)(() => {
4797
5935
  const clamp = () => {
4798
5936
  const el = popRef.current;
4799
5937
  if (!el) return;
@@ -4823,24 +5961,24 @@ var __defProp = Object.defineProperty;
4823
5961
  }
4824
5962
 
4825
5963
  // client/src/pill/MemoryModePill.tsx
4826
- var import_jsx_runtime20 = require("react/jsx-runtime");
5964
+ var import_jsx_runtime22 = require("react/jsx-runtime");
4827
5965
  function MemoryModePill(props) {
4828
5966
  const rpc = props.rpc;
4829
5967
  const sessionId = props.sessionId || props.session && props.session.sessionId;
4830
- const [mode, setMode] = (0, import_react20.useState)(null);
4831
- const [recall, setRecall] = (0, import_react20.useState)(null);
4832
- const [recallResolved, setRecallResolved] = (0, import_react20.useState)(true);
4833
- const [halls, setWings] = (0, import_react20.useState)([]);
4834
- const [hallIncludeUnlabeled, setWingIncludeUnlabeled] = (0, import_react20.useState)(true);
4835
- const [hallIncludeGeneral, setWingIncludeGeneral] = (0, import_react20.useState)(false);
4836
- const [wingLabels, setHallLabels] = (0, import_react20.useState)({});
4837
- const [error, setError] = (0, import_react20.useState)(null);
4838
- const [open, setOpen] = (0, import_react20.useState)(false);
4839
- const wrapRef = (0, import_react20.useRef)(null);
4840
- const popRef = (0, import_react20.useRef)(null);
5968
+ const [mode, setMode] = (0, import_react22.useState)(null);
5969
+ const [recall, setRecall] = (0, import_react22.useState)(null);
5970
+ const [recallResolved, setRecallResolved] = (0, import_react22.useState)(true);
5971
+ const [halls, setWings] = (0, import_react22.useState)([]);
5972
+ const [hallIncludeUnlabeled, setWingIncludeUnlabeled] = (0, import_react22.useState)(true);
5973
+ const [hallIncludeGeneral, setWingIncludeGeneral] = (0, import_react22.useState)(false);
5974
+ const [wingLabels, setHallLabels] = (0, import_react22.useState)({});
5975
+ const [error, setError] = (0, import_react22.useState)(null);
5976
+ const [open, setOpen] = (0, import_react22.useState)(false);
5977
+ const wrapRef = (0, import_react22.useRef)(null);
5978
+ const popRef = (0, import_react22.useRef)(null);
4841
5979
  const shiftX = useViewportClamp(popRef);
4842
- const seqRef = (0, import_react20.useRef)(0);
4843
- const load = (0, import_react20.useCallback)(() => {
5980
+ const seqRef = (0, import_react22.useRef)(0);
5981
+ const load = (0, import_react22.useCallback)(() => {
4844
5982
  if (!sessionId || !rpc) return;
4845
5983
  const token = ++seqRef.current;
4846
5984
  setError(null);
@@ -4865,13 +6003,13 @@ var __defProp = Object.defineProperty;
4865
6003
  }).catch(() => {
4866
6004
  });
4867
6005
  }, [sessionId, rpc]);
4868
- (0, import_react20.useEffect)(() => {
6006
+ (0, import_react22.useEffect)(() => {
4869
6007
  load();
4870
6008
  }, [load]);
4871
- (0, import_react20.useEffect)(() => {
6009
+ (0, import_react22.useEffect)(() => {
4872
6010
  watchSidebarIcon();
4873
6011
  }, []);
4874
- (0, import_react20.useEffect)(() => {
6012
+ (0, import_react22.useEffect)(() => {
4875
6013
  initOccupancyIndicator(
4876
6014
  (endpoint, payload) => rpc(endpoint, payload)
4877
6015
  );
@@ -4879,7 +6017,7 @@ var __defProp = Object.defineProperty;
4879
6017
  watchContextMeter();
4880
6018
  noteOccupancySession(sessionId ?? null);
4881
6019
  }, [sessionId, rpc]);
4882
- (0, import_react20.useEffect)(() => {
6020
+ (0, import_react22.useEffect)(() => {
4883
6021
  if (!open) return;
4884
6022
  const onDown = (e) => {
4885
6023
  if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
@@ -5019,8 +6157,8 @@ var __defProp = Object.defineProperty;
5019
6157
  pillStyle.boxShadow = "0 0 12px color-mix(in srgb, " + info.color + " 30%, transparent)";
5020
6158
  pillStyle["--dsh-mem-pill-tint"] = info.color;
5021
6159
  }
5022
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { ref: wrapRef, style: { position: "relative", display: "inline-flex" }, children: [
5023
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
6160
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { ref: wrapRef, style: { position: "relative", display: "inline-flex" }, children: [
6161
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
5024
6162
  "button",
5025
6163
  {
5026
6164
  type: "button",
@@ -5033,11 +6171,11 @@ var __defProp = Object.defineProperty;
5033
6171
  style: pillStyle,
5034
6172
  children: [
5035
6173
  "记忆 · ",
5036
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { children: faceLabel })
6174
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: faceLabel })
5037
6175
  ]
5038
6176
  }
5039
6177
  ),
5040
- open ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6178
+ open ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5041
6179
  "div",
5042
6180
  {
5043
6181
  ref: popRef,
@@ -5048,12 +6186,12 @@ var __defProp = Object.defineProperty;
5048
6186
  transform: "translateX(calc(-50% + " + shiftX + "px))",
5049
6187
  zIndex: 1e3
5050
6188
  },
5051
- children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6189
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5052
6190
  "div",
5053
6191
  {
5054
6192
  className: "dsh-mem-popover",
5055
6193
  style: { position: "relative", padding: "10px 12px" },
5056
- children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6194
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5057
6195
  WingWheel,
5058
6196
  {
5059
6197
  mode: mode || "auto",