dsh-prime-memory 0.18.5 → 0.18.7

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_react19 = require("react");
35
+ var import_react17 = 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 btn3 = wrapRef.current.querySelector("button");
1649
- if (btn3 && btn3.focus) btn3.focus();
1648
+ const btn = wrapRef.current.querySelector("button");
1649
+ if (btn && btn.focus) btn.focus();
1650
1650
  }
1651
1651
  };
1652
1652
  const pick = (id) => {
@@ -3287,11 +3287,10 @@ 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);
3292
3290
  const [retiredFilter, setRetiredFilter] = (0, import_react15.useState)("");
3291
+ const [rooms, setRooms] = (0, import_react15.useState)([]);
3293
3292
  const [wingCatalog, setHallCatalog] = (0, import_react15.useState)(null);
3294
- const [last, setLast] = (0, import_react15.useState)({ query: "", type: "", scene: "", halls: [], tag: "", untagged: false, retired: "" });
3293
+ const [last, setLast] = (0, import_react15.useState)({ query: "", type: "", scene: "", halls: [], tag: "", retired: "" });
3295
3294
  const seqRef = (0, import_react15.useRef)(0);
3296
3295
  const fetchPage = (0, import_react15.useCallback)(
3297
3296
  (conds, offset, append) => {
@@ -3304,7 +3303,6 @@ var __defProp = Object.defineProperty;
3304
3303
  if (conds.scene) payload.scene = conds.scene;
3305
3304
  if (conds.halls.length > 0) payload.halls = conds.halls;
3306
3305
  if (conds.tag) payload.tag = conds.tag;
3307
- if (conds.untagged) payload.untagged = true;
3308
3306
  const retiredSel = retiredSelOf(conds.retired);
3309
3307
  if (retiredSel !== void 0) payload.retired = retiredSel;
3310
3308
  rpc("dsh-memory/list-records", payload).then((r) => {
@@ -3316,7 +3314,6 @@ var __defProp = Object.defineProperty;
3316
3314
  }
3317
3315
  const v = r.value;
3318
3316
  setItems((prev) => append ? prev.concat(v.items) : v.items);
3319
- if (v.usageSummary) setUsageSummary(v.usageSummary);
3320
3317
  if (!append) setSel(/* @__PURE__ */ new Set());
3321
3318
  setHasMore(!!v.hasMore);
3322
3319
  setTotal(v.total === void 0 || v.total === null ? null : v.total);
@@ -3332,24 +3329,24 @@ var __defProp = Object.defineProperty;
3332
3329
  [rpc]
3333
3330
  );
3334
3331
  const search = () => {
3335
- const conds = { query: query.trim(), type: typeFilter, scene: sceneFilter, halls: hallFilter, tag: tagFilter, untagged: untaggedOnly, retired: retiredFilter };
3332
+ const conds = { query: query.trim(), type: typeFilter, scene: sceneFilter, halls: hallFilter, tag: tagFilter, retired: retiredFilter };
3336
3333
  setLast(conds);
3337
3334
  fetchPage(conds, 0, false);
3338
3335
  };
3339
3336
  const applyRetiredFilter = (next) => {
3340
3337
  setRetiredFilter(next);
3341
- const conds = { query: query.trim(), type: typeFilter, scene: sceneFilter, halls: hallFilter, tag: tagFilter, untagged: untaggedOnly, retired: next };
3338
+ const conds = { query: query.trim(), type: typeFilter, scene: sceneFilter, halls: hallFilter, tag: tagFilter, retired: next };
3342
3339
  setLast(conds);
3343
3340
  fetchPage(conds, 0, false);
3344
3341
  };
3345
3342
  const loadRooms = (0, import_react15.useCallback)(() => {
3346
3343
  rpc("dsh-memory/rooms-get", {}).then((r) => {
3347
- if (r && r.ok) setOrphanCount(r.value.orphanCount ?? 0);
3344
+ if (r && r.ok) setRooms(r.value.rooms ?? []);
3348
3345
  }).catch(() => {
3349
3346
  });
3350
3347
  }, [rpc]);
3351
3348
  (0, import_react15.useEffect)(() => {
3352
- fetchPage({ query: "", type: "", scene: "", halls: [], tag: "", untagged: false, retired: "" }, 0, false);
3349
+ fetchPage({ query: "", type: "", scene: "", halls: [], tag: "", retired: "" }, 0, false);
3353
3350
  loadRooms();
3354
3351
  }, [fetchPage, loadRooms]);
3355
3352
  const loadHiPriv = (0, import_react15.useCallback)(() => {
@@ -3452,7 +3449,6 @@ var __defProp = Object.defineProperty;
3452
3449
  unknown: "已退场"
3453
3450
  };
3454
3451
  const countText = total !== null ? "共 " + total + " 条" : items.length + " 条" + (hasMore ? "+" : "");
3455
- const [usageSummary, setUsageSummary] = (0, import_react15.useState)(null);
3456
3452
  const selCount = sel.size;
3457
3453
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [
3458
3454
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: S.toolbar, children: [
@@ -3519,86 +3515,40 @@ var __defProp = Object.defineProperty;
3519
3515
  }
3520
3516
  )
3521
3517
  ] }),
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)(
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)(
3552
3523
  "button",
3553
3524
  {
3554
3525
  type: "button",
3555
- title: "清除 Room/孤儿筛选",
3526
+ title: on ? "取消按该 Room 筛选" : "按该 Room 筛选记录",
3556
3527
  onClick: () => {
3557
- setTagFilter("");
3558
- setUntaggedOnly(false);
3559
- const conds = { query: query.trim(), type: typeFilter, scene: sceneFilter, halls: hallFilter, tag: "", untagged: false, retired: retiredFilter };
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 };
3560
3531
  setLast(conds);
3561
3532
  fetchPage(conds, 0, false);
3562
3533
  },
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);
3584
- },
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)"
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
3593
3544
  },
3594
- children: "导出孤儿"
3595
- }
3596
- ),
3597
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: S.muted, children: "Room 标签/分类管理:见「Hall」「Room」标签页。" })
3598
- ] }),
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 分类:暂无(由反刍涌现的标签自动生成,无需手工建立)" }),
3599
3550
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: { ...S.flexRow, marginBottom: 10 }, children: [
3600
3551
  /* @__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,
3602
3552
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: S.muted, children: "退场筛查" }),
3603
3553
  [["", "全部"], ["active", "仅活跃"], ["retired", "仅退场"]].map(([val, label]) => {
3604
3554
  const on = retiredFilter === val;
@@ -3702,29 +3652,6 @@ var __defProp = Object.defineProperty;
3702
3652
  }
3703
3653
  ) : null,
3704
3654
  /* @__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,
3728
3655
  m.score !== null && m.score !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: S.muted, children: "相关度 " + Number(m.score).toFixed(2) }) : null,
3729
3656
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: S.grow }),
3730
3657
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: S.muted, children: fmtTime(m.updatedAt) }),
@@ -3847,1075 +3774,14 @@ var __defProp = Object.defineProperty;
3847
3774
  ] });
3848
3775
  }
3849
3776
 
3850
- // client/src/tabs/RoomsTab.tsx
3777
+ // client/src/tabs/ScenesTab.tsx
3851
3778
  var import_react16 = require("react");
3852
3779
  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");
4914
3780
  function SceneCard(props) {
4915
3781
  const s = props.s;
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)(
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)(
4919
3785
  "div",
4920
3786
  {
4921
3787
  style: { ...S.sceneHead, cursor: "pointer", userSelect: "none" },
@@ -4923,23 +3789,23 @@ var __defProp = Object.defineProperty;
4923
3789
  setOpen(!open);
4924
3790
  },
4925
3791
  children: [
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) })
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) })
4931
3797
  ]
4932
3798
  }
4933
3799
  ),
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
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
4936
3802
  ] });
4937
3803
  }
4938
3804
  function ScenesTab(props) {
4939
3805
  const rpc = props.rpc;
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)(() => {
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)(() => {
4943
3809
  rpc("dsh-memory/scenes", {}).then((r) => {
4944
3810
  if (r && r.ok) {
4945
3811
  setItems(r.value.items);
@@ -4949,58 +3815,54 @@ var __defProp = Object.defineProperty;
4949
3815
  setError(String(e && e.message || e));
4950
3816
  });
4951
3817
  }, [rpc]);
4952
- (0, import_react18.useEffect)(() => {
3818
+ (0, import_react16.useEffect)(() => {
4953
3819
  load();
4954
3820
  }, [load]);
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: "刷新" })
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: "刷新" })
4960
3826
  ] }),
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,
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,
4963
3829
  (items || []).map((s) => {
4964
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SceneCard, { s }, s.path);
3830
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(SceneCard, { s }, s.path);
4965
3831
  })
4966
3832
  ] });
4967
3833
  }
4968
3834
 
4969
3835
  // client/src/panel.tsx
4970
- var import_jsx_runtime19 = require("react/jsx-runtime");
3836
+ var import_jsx_runtime17 = require("react/jsx-runtime");
4971
3837
  var TABS = [
4972
3838
  ["overview", "概览"],
4973
3839
  ["records", "记忆"],
4974
3840
  ["conflicts", "冲突"],
4975
3841
  ["scenes", "场景"],
4976
- ["rooms", "Hall"],
4977
- ["room", "Room"],
4978
3842
  ["persona", "画像"],
4979
3843
  ["cost", "成本"],
4980
3844
  ["log", "日志"]
4981
3845
  ];
4982
3846
  function MemoryPanel(props) {
4983
3847
  const rpc = props.rpc;
4984
- const [tab, setTab] = (0, import_react19.useState)("overview");
3848
+ const [tab, setTab] = (0, import_react17.useState)("overview");
4985
3849
  ensureThemeStyle();
4986
- (0, import_react19.useEffect)(() => {
3850
+ (0, import_react17.useEffect)(() => {
4987
3851
  watchSidebarIcon();
4988
3852
  }, []);
4989
3853
  let body;
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)(
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)(
5004
3866
  "button",
5005
3867
  {
5006
3868
  className: tab === t[0] ? "dsh-mem-tab dsh-mem-tab-on" : "dsh-mem-tab",
@@ -5017,7 +3879,7 @@ var __defProp = Object.defineProperty;
5017
3879
  }
5018
3880
 
5019
3881
  // client/src/pill/MemoryModePill.tsx
5020
- var import_react22 = require("react");
3882
+ var import_react20 = require("react");
5021
3883
 
5022
3884
  // src/util/context-occupancy.ts
5023
3885
  var CONTEXT_METER_CIRCUMFERENCE = 34.55751918948772;
@@ -5333,23 +4195,23 @@ var __defProp = Object.defineProperty;
5333
4195
  }
5334
4196
 
5335
4197
  // client/src/pill/WingWheel.tsx
5336
- var import_react21 = require("react");
4198
+ var import_react19 = require("react");
5337
4199
 
5338
4200
  // client/src/pill/SessionInfoArea.tsx
5339
- var import_react20 = require("react");
5340
- var import_jsx_runtime20 = require("react/jsx-runtime");
4201
+ var import_react18 = require("react");
4202
+ var import_jsx_runtime18 = require("react/jsx-runtime");
5341
4203
  function sinfoCell(val, label, title) {
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 })
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 })
5345
4207
  ] });
5346
4208
  }
5347
4209
  function SessionInfoArea(props) {
5348
4210
  const rpc = props.rpc;
5349
4211
  const sessionId = props.sessionId;
5350
- const [stats, setStats] = (0, import_react20.useState)(void 0);
5351
- const busyRef = (0, import_react20.useRef)(false);
5352
- (0, import_react20.useEffect)(() => {
4212
+ const [stats, setStats] = (0, import_react18.useState)(void 0);
4213
+ const busyRef = (0, import_react18.useRef)(false);
4214
+ (0, import_react18.useEffect)(() => {
5353
4215
  if (!rpc || !sessionId) return void 0;
5354
4216
  let alive = true;
5355
4217
  let timer = null;
@@ -5382,7 +4244,7 @@ var __defProp = Object.defineProperty;
5382
4244
  }, [rpc, sessionId]);
5383
4245
  if (stats === null) return null;
5384
4246
  if (stats === void 0) {
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: [
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: [
5386
4248
  sinfoCell("…", "召回命中"),
5387
4249
  sinfoCell("…", "攒批进度"),
5388
4250
  sinfoCell("…", "本会话记忆"),
@@ -5431,21 +4293,21 @@ var __defProp = Object.defineProperty;
5431
4293
  else if (stats.retrieval === "none") note = "检索不可用(FTS 与向量均失效)";
5432
4294
  }
5433
4295
  const ago = fmtAgo(gl.lastExtractAt);
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: [
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: [
5437
4299
  sinfoCell(rcVal, rcLabel, rcTitle),
5438
4300
  sinfoCell(dVal, dLabel, dTitle),
5439
4301
  sinfoCell(String(di.producedRecords || 0), "本会话记忆", pTitle),
5440
4302
  sinfoCell(stats.l0Count != null ? String(stats.l0Count) : "…", "会话消息")
5441
4303
  ] }),
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 || "尚未蒸馏") })
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 || "尚未蒸馏") })
5444
4306
  ] });
5445
4307
  }
5446
4308
 
5447
4309
  // client/src/pill/WingWheel.tsx
5448
- var import_jsx_runtime21 = require("react/jsx-runtime");
4310
+ var import_jsx_runtime19 = require("react/jsx-runtime");
5449
4311
  var SIZE = 196;
5450
4312
  var CENTER = SIZE / 2;
5451
4313
  var R = 64;
@@ -5488,17 +4350,17 @@ var __defProp = Object.defineProperty;
5488
4350
  function WingWheel(props) {
5489
4351
  ensureThemeStyle();
5490
4352
  const isOff = props.mode === "off";
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)(
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)(
5496
4358
  () => () => {
5497
4359
  for (const t of timersRef.current) window.clearTimeout(t);
5498
4360
  },
5499
4361
  []
5500
4362
  );
5501
- (0, import_react21.useEffect)(() => {
4363
+ (0, import_react19.useEffect)(() => {
5502
4364
  let alive = true;
5503
4365
  props.rpc("dsh-memory/wing-overview", {}).then((r) => {
5504
4366
  if (!alive) return;
@@ -5544,28 +4406,28 @@ var __defProp = Object.defineProperty;
5544
4406
  finish();
5545
4407
  });
5546
4408
  };
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);
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);
5553
4415
  const lockedIndex = (() => {
5554
4416
  if (props.halls.length !== 1 || !overview) return null;
5555
4417
  const i = overview.corners.findIndex((c) => c.id === props.halls[0]);
5556
4418
  return i >= 0 ? i : null;
5557
4419
  })();
5558
4420
  const activeCorner = drag ? drag.index : lockedIndex;
5559
- const blockPosRef = (0, import_react21.useRef)(
4421
+ const blockPosRef = (0, import_react19.useRef)(
5560
4422
  activeCorner != null ? cornerXY(activeCorner) : { x: CENTER, y: CENTER }
5561
4423
  );
5562
- const blockTargetRef = (0, import_react21.useRef)(blockPosRef.current);
5563
- const [blockPos, setBlockPos] = (0, import_react21.useState)(blockPosRef.current);
5564
- (0, import_react21.useEffect)(() => {
4424
+ const blockTargetRef = (0, import_react19.useRef)(blockPosRef.current);
4425
+ const [blockPos, setBlockPos] = (0, import_react19.useState)(blockPosRef.current);
4426
+ (0, import_react19.useEffect)(() => {
5565
4427
  blockTargetRef.current = activeCorner != null ? cornerXY(activeCorner) : { x: CENTER, y: CENTER };
5566
4428
  }, [activeCorner]);
5567
- const reducedMotionRef = (0, import_react21.useRef)(false);
5568
- (0, import_react21.useEffect)(() => {
4429
+ const reducedMotionRef = (0, import_react19.useRef)(false);
4430
+ (0, import_react19.useEffect)(() => {
5569
4431
  const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
5570
4432
  const apply2 = () => {
5571
4433
  reducedMotionRef.current = mq.matches;
@@ -5574,7 +4436,7 @@ var __defProp = Object.defineProperty;
5574
4436
  mq.addEventListener("change", apply2);
5575
4437
  return () => mq.removeEventListener("change", apply2);
5576
4438
  }, []);
5577
- (0, import_react21.useEffect)(() => {
4439
+ (0, import_react19.useEffect)(() => {
5578
4440
  let raf = 0;
5579
4441
  const loop = () => {
5580
4442
  const cur = blockPosRef.current;
@@ -5663,8 +4525,8 @@ var __defProp = Object.defineProperty;
5663
4525
  }).join(" ");
5664
4526
  const grayStyle = isOff ? { opacity: 0.45, pointerEvents: "none" } : {};
5665
4527
  const boundariesDisabled = props.halls.length === 0 || isOff;
5666
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { style: { width: SIZE }, children: [
5667
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
4528
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { width: SIZE }, children: [
4529
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5668
4530
  "div",
5669
4531
  {
5670
4532
  ref: boxRef,
@@ -5674,7 +4536,7 @@ var __defProp = Object.defineProperty;
5674
4536
  onPointerUp: onBoxUp,
5675
4537
  onPointerCancel: onBoxUp,
5676
4538
  children: [
5677
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
4539
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5678
4540
  "svg",
5679
4541
  {
5680
4542
  width: SIZE,
@@ -5682,7 +4544,7 @@ var __defProp = Object.defineProperty;
5682
4544
  style: { position: "absolute", inset: 0, pointerEvents: "none" },
5683
4545
  "aria-hidden": "true",
5684
4546
  children: [
5685
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4547
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5686
4548
  "polygon",
5687
4549
  {
5688
4550
  points: polyPoints,
@@ -5693,7 +4555,7 @@ var __defProp = Object.defineProperty;
5693
4555
  opacity: 0.45
5694
4556
  }
5695
4557
  ),
5696
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4558
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5697
4559
  "polygon",
5698
4560
  {
5699
4561
  points: polyPoints,
@@ -5704,7 +4566,7 @@ var __defProp = Object.defineProperty;
5704
4566
  ),
5705
4567
  [0, 1, 2, 3, 4, 5, 6, 7].map((i) => {
5706
4568
  const p = cornerPos(i);
5707
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4569
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5708
4570
  "line",
5709
4571
  {
5710
4572
  x1: CENTER,
@@ -5720,7 +4582,7 @@ var __defProp = Object.defineProperty;
5720
4582
  ]
5721
4583
  }
5722
4584
  ),
5723
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4585
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5724
4586
  "div",
5725
4587
  {
5726
4588
  "aria-hidden": "true",
@@ -5739,7 +4601,7 @@ var __defProp = Object.defineProperty;
5739
4601
  }
5740
4602
  }
5741
4603
  ),
5742
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4604
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5743
4605
  "button",
5744
4606
  {
5745
4607
  type: "button",
@@ -5773,7 +4635,7 @@ var __defProp = Object.defineProperty;
5773
4635
  const dim = activeCorner != null && !active;
5774
4636
  const empty = count === 0;
5775
4637
  const p = cornerPos(i);
5776
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
4638
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5777
4639
  "div",
5778
4640
  {
5779
4641
  title: id ? `「${label}」${count ?? 0} 条
@@ -5802,8 +4664,8 @@ var __defProp = Object.defineProperty;
5802
4664
  zIndex: 2
5803
4665
  },
5804
4666
  children: [
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} 条` })
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} 条` })
5807
4669
  ]
5808
4670
  },
5809
4671
  id ?? i
@@ -5812,7 +4674,7 @@ var __defProp = Object.defineProperty;
5812
4674
  ]
5813
4675
  }
5814
4676
  ),
5815
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
4677
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5816
4678
  "div",
5817
4679
  {
5818
4680
  title: props.halls.length === 0 ? "选定某个角(单 Wing)后,这两项才生效" : "锁定 Wing 时两类无角记忆是否参与召回",
@@ -5826,9 +4688,9 @@ var __defProp = Object.defineProperty;
5826
4688
  pointerEvents: boundariesDisabled ? "none" : void 0
5827
4689
  },
5828
4690
  children: [
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)(
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)(
5832
4694
  Segmented,
5833
4695
  {
5834
4696
  value: props.hallIncludeUnlabeled ? "in" : "ex",
@@ -5839,7 +4701,7 @@ var __defProp = Object.defineProperty;
5839
4701
  onChange: (key) => props.onCommitWingBoundaries({ includeUnlabeled: key === "in" })
5840
4702
  }
5841
4703
  ),
5842
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4704
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5843
4705
  Segmented,
5844
4706
  {
5845
4707
  value: props.hallIncludeGeneral ? "in" : "ex",
@@ -5854,7 +4716,7 @@ var __defProp = Object.defineProperty;
5854
4716
  ]
5855
4717
  }
5856
4718
  ),
5857
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Row, { label: "会话", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4719
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Row, { label: "会话", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5858
4720
  Segmented,
5859
4721
  {
5860
4722
  value: isOff ? "off" : "on",
@@ -5869,7 +4731,7 @@ var __defProp = Object.defineProperty;
5869
4731
  onChange: (key) => props.onCommit(key === "off" ? "off" : "auto")
5870
4732
  }
5871
4733
  ) }),
5872
- props.recall !== void 0 && props.onCommitRecall ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Row, { label: "注入", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4734
+ props.recall !== void 0 && props.onCommitRecall ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Row, { label: "注入", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
5873
4735
  Segmented,
5874
4736
  {
5875
4737
  value: props.recall === null ? "follow" : props.recall ? "on" : "off",
@@ -5882,7 +4744,7 @@ var __defProp = Object.defineProperty;
5882
4744
  onChange: (key) => props.onCommitRecall(key === "on" ? true : key === "off" ? false : null)
5883
4745
  }
5884
4746
  ) }) : null,
5885
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
4747
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5886
4748
  "div",
5887
4749
  {
5888
4750
  style: {
@@ -5893,8 +4755,8 @@ var __defProp = Object.defineProperty;
5893
4755
  marginTop: 10
5894
4756
  },
5895
4757
  children: [
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)(
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)(
5898
4760
  ActionButton,
5899
4761
  {
5900
4762
  label: backfillBusy ? "回填中…" : "回填",
@@ -5906,12 +4768,12 @@ var __defProp = Object.defineProperty;
5906
4768
  ]
5907
4769
  }
5908
4770
  ),
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 })
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 })
5911
4773
  ] });
5912
4774
  }
5913
4775
  function Row(props) {
5914
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
4776
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
5915
4777
  "div",
5916
4778
  {
5917
4779
  style: {
@@ -5921,7 +4783,7 @@ var __defProp = Object.defineProperty;
5921
4783
  marginTop: 10
5922
4784
  },
5923
4785
  children: [
5924
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { style: { fontSize: 12, color: "var(--dsh-mem-text-3)" }, children: props.label }),
4786
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { style: { fontSize: 12, color: "var(--dsh-mem-text-3)" }, children: props.label }),
5925
4787
  props.children
5926
4788
  ]
5927
4789
  }
@@ -5929,9 +4791,9 @@ var __defProp = Object.defineProperty;
5929
4791
  }
5930
4792
  var LABEL_FALLBACK = ["工作", "人际", "学习", "创作娱乐", "居家", "健康", "财务", "出行"];
5931
4793
  function useViewportClamp(popRef) {
5932
- const shiftRef = (0, import_react21.useRef)(0);
5933
- const [shiftX, setShiftX] = (0, import_react21.useState)(0);
5934
- (0, import_react21.useEffect)(() => {
4794
+ const shiftRef = (0, import_react19.useRef)(0);
4795
+ const [shiftX, setShiftX] = (0, import_react19.useState)(0);
4796
+ (0, import_react19.useEffect)(() => {
5935
4797
  const clamp = () => {
5936
4798
  const el = popRef.current;
5937
4799
  if (!el) return;
@@ -5961,24 +4823,24 @@ var __defProp = Object.defineProperty;
5961
4823
  }
5962
4824
 
5963
4825
  // client/src/pill/MemoryModePill.tsx
5964
- var import_jsx_runtime22 = require("react/jsx-runtime");
4826
+ var import_jsx_runtime20 = require("react/jsx-runtime");
5965
4827
  function MemoryModePill(props) {
5966
4828
  const rpc = props.rpc;
5967
4829
  const sessionId = props.sessionId || props.session && props.session.sessionId;
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);
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);
5979
4841
  const shiftX = useViewportClamp(popRef);
5980
- const seqRef = (0, import_react22.useRef)(0);
5981
- const load = (0, import_react22.useCallback)(() => {
4842
+ const seqRef = (0, import_react20.useRef)(0);
4843
+ const load = (0, import_react20.useCallback)(() => {
5982
4844
  if (!sessionId || !rpc) return;
5983
4845
  const token = ++seqRef.current;
5984
4846
  setError(null);
@@ -6003,13 +4865,13 @@ var __defProp = Object.defineProperty;
6003
4865
  }).catch(() => {
6004
4866
  });
6005
4867
  }, [sessionId, rpc]);
6006
- (0, import_react22.useEffect)(() => {
4868
+ (0, import_react20.useEffect)(() => {
6007
4869
  load();
6008
4870
  }, [load]);
6009
- (0, import_react22.useEffect)(() => {
4871
+ (0, import_react20.useEffect)(() => {
6010
4872
  watchSidebarIcon();
6011
4873
  }, []);
6012
- (0, import_react22.useEffect)(() => {
4874
+ (0, import_react20.useEffect)(() => {
6013
4875
  initOccupancyIndicator(
6014
4876
  (endpoint, payload) => rpc(endpoint, payload)
6015
4877
  );
@@ -6017,7 +4879,7 @@ var __defProp = Object.defineProperty;
6017
4879
  watchContextMeter();
6018
4880
  noteOccupancySession(sessionId ?? null);
6019
4881
  }, [sessionId, rpc]);
6020
- (0, import_react22.useEffect)(() => {
4882
+ (0, import_react20.useEffect)(() => {
6021
4883
  if (!open) return;
6022
4884
  const onDown = (e) => {
6023
4885
  if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
@@ -6157,8 +5019,8 @@ var __defProp = Object.defineProperty;
6157
5019
  pillStyle.boxShadow = "0 0 12px color-mix(in srgb, " + info.color + " 30%, transparent)";
6158
5020
  pillStyle["--dsh-mem-pill-tint"] = info.color;
6159
5021
  }
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)(
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)(
6162
5024
  "button",
6163
5025
  {
6164
5026
  type: "button",
@@ -6171,11 +5033,11 @@ var __defProp = Object.defineProperty;
6171
5033
  style: pillStyle,
6172
5034
  children: [
6173
5035
  "记忆 · ",
6174
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: faceLabel })
5036
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { children: faceLabel })
6175
5037
  ]
6176
5038
  }
6177
5039
  ),
6178
- open ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5040
+ open ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6179
5041
  "div",
6180
5042
  {
6181
5043
  ref: popRef,
@@ -6186,12 +5048,12 @@ var __defProp = Object.defineProperty;
6186
5048
  transform: "translateX(calc(-50% + " + shiftX + "px))",
6187
5049
  zIndex: 1e3
6188
5050
  },
6189
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5051
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6190
5052
  "div",
6191
5053
  {
6192
5054
  className: "dsh-mem-popover",
6193
5055
  style: { position: "relative", padding: "10px 12px" },
6194
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5056
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
6195
5057
  WingWheel,
6196
5058
  {
6197
5059
  mode: mode || "auto",