dsh-prime-memory 0.20.0-beta.24 → 0.20.0-beta.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -17,6 +17,25 @@
17
17
  - **建议状态(`longtask-hint-get`)以纯端点提供**:判定 = 上下文占用达 `contextThresholdPct` 或 todo 漂移达 `driftThreshold`(部署配置可调);配合 `longtask-compress-tail` 作手动压缩入口。
18
18
  - **撤下浮动球 UI(`client/src/longtask-fab.ts`;beta.1 起曾随包发布)**:悬浮建议面板的归属本在 context-compression 侧(「建议启动压缩」是其领域),pm 属误载——2026-10-02 用户裁定撤下。撤下不违座位契约(settings.section 恰一个 + 输入栏 pill,body 单例不占座位),但全应用级 overlay 会与其他插件的悬浮 UI 抢占屏幕角落;长任务开关与手动压缩入口待长任务定版时落记忆设置分节。
19
19
  - **`session-mode-set` 的 `mode` 转为可选**:与 `longTask` 二选一必传——纯切长任务开关不再被迫发送档位(旧客户端恒传 mode,行为不变)。响应 `mode` 改回当前生效值。
20
+ ## [0.20.0-beta.26] — 2026-10-03
21
+
22
+ ### 新增(Hall/Room 增长后的可用性)
23
+
24
+ - **Hall/Room 名称搜索过滤**:Hall 页新增搜索框(slug、显示名、别名,大小写不敏感)
25
+ —— hall 自身命中显示全部子 room,否则只留命中的子 room,整组无命中即隐藏;
26
+ 搜索时匹配组自动展开(折叠态不再藏住结果);自生长未注册区同滤。
27
+ Room 页同款搜索框,与退场筛选 chips 叠加生效。
28
+ - **Hall 分组折叠,默认全折叠**:hall 头只显示「合计 N 条 · M 个 room」,点头部或
29
+ ▸/▾ 钮展开/收起子 room;「展开全部/收起全部」批量钮;展开状态 localStorage 记忆
30
+ (`dsh.memory.halls.open`)。归类点选(点 hall 头完成归类)优先级不变。
31
+ ## [0.20.0-beta.25] — 2026-10-02
32
+
33
+ > 自生长 room 退场后状态不可见修复 + Hall 子 room 显示规则(用户裁定)。
34
+
35
+ ### 修复/变更
36
+
37
+ - **自生长未注册 room 退场即变「已退役/恢复」**:此前退场只软删记录,词表无条目可变,按钮永远是「收编/退场」。现 retire withRecords 对未注册 slug **自动补一条 grown 注册条目并置 retired**——面板行即时切到「已退役」态,「恢复」(active:true)后词表回归。持久化,重启不丢。
38
+ - **Hall 页子 room 只显示 active**:retired/退场的 room 不再出现在 hall 分组下(去 Room 页看,含「已退役」筛选);分组聚合计数随之只含 active 子类。
20
39
  ## [0.20.0-beta.24] — 2026-10-02
21
40
 
22
41
  ### 修复
package/dist/client.js CHANGED
@@ -3897,6 +3897,15 @@ var __defProp = Object.defineProperty;
3897
3897
  const [editing, setEditing] = (0, import_react16.useState)(null);
3898
3898
  const [expandedRooms, setExpandedRooms] = (0, import_react16.useState)(/* @__PURE__ */ new Set());
3899
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)("");
3900
3909
  const dragRef = (0, import_react16.useRef)(null);
3901
3910
  const [ghost, setGhost] = (0, import_react16.useState)(null);
3902
3911
  const onDragLabelDown = (ev, slug) => {
@@ -4093,6 +4102,7 @@ var __defProp = Object.defineProperty;
4093
4102
  const countOf = new Map(rooms.map((r) => [r.room, r.count]));
4094
4103
  const byHall = /* @__PURE__ */ new Map();
4095
4104
  for (const e of registry) {
4105
+ if (e.status !== "active") continue;
4096
4106
  const hall = majorOf(e.slug);
4097
4107
  const list = byHall.get(hall);
4098
4108
  if (list) list.push(e);
@@ -4106,6 +4116,39 @@ var __defProp = Object.defineProperty;
4106
4116
  })).sort((a, b) => a.hall.localeCompare(b.hall));
4107
4117
  }, [registry, rooms]);
4108
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
+ );
4109
4152
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { children: [
4110
4153
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { ...S.flexRow, marginBottom: 8 }, children: [
4111
4154
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: "Hall = Room 大类 · 大类上限 200,小类「大类/小类」细分不占额度 · 与 Wing(域)筛选无关" }),
@@ -4164,6 +4207,22 @@ var __defProp = Object.defineProperty;
4164
4207
  ),
4165
4208
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", style: btn, onClick: loadAll, title: "重拉注册表与计数", children: "刷新" })
4166
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
+ ] }),
4167
4226
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, alignItems: "center", marginBottom: 10 }, children: [
4168
4227
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4169
4228
  "input",
@@ -4267,163 +4326,187 @@ var __defProp = Object.defineProperty;
4267
4326
  msg ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: S.hint, children: msg }) : null,
4268
4327
  error ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: S.error, children: error }) : null,
4269
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,
4270
- groups.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: S.intro, children: "注册表为空。在上方注册 Hall/Room(如 dsh-plugin),或把自生长 slug「收编」进词表 —— 之后候选标注器会优先把相关记忆挂到这些 slug 上。" }) : groups.map((g) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { marginBottom: 10, border: "1px solid var(--dsh-mem-border)", borderRadius: 8, overflow: "hidden" }, children: [
4271
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
4272
- "div",
4273
- {
4274
- "data-hall": g.hall,
4275
- onDragOver: (ev) => {
4276
- if (!hiPriv || busy) return;
4277
- ev.preventDefault();
4278
- ev.dataTransfer.dropEffect = "move";
4279
- setDragOverHall(g.hall);
4280
- },
4281
- onDragLeave: () => setDragOverHall((cur) => cur === g.hall ? null : cur),
4282
- onDrop: (ev) => {
4283
- ev.preventDefault();
4284
- setDragOverHall(null);
4285
- if (!hiPriv || busy) return;
4286
- const from = ev.dataTransfer.getData("text/dsh-room");
4287
- if (from) beginReclass(from, g.hall);
4288
- },
4289
- onClick: () => {
4290
- if (!picking || busy) return;
4291
- const from = picking;
4292
- setPicking(null);
4293
- beginReclass(from, g.hall);
4294
- },
4295
- style: {
4296
- display: "flex",
4297
- alignItems: "center",
4298
- gap: 8,
4299
- padding: "5px 10px",
4300
- background: "var(--dsh-mem-bg-inset)",
4301
- outline: dragOverHall === g.hall || picking !== null ? "2px dashed var(--dsh-mem-accent)" : void 0,
4302
- outlineOffset: dragOverHall === g.hall || picking !== null ? "-2px" : void 0,
4303
- cursor: picking ? "pointer" : void 0
4304
- },
4305
- title: picking ? "点击此 hall:把「" + picking + "」归入 " + g.hall + " 下(major/minor,预览后确认)" : hiPriv ? "拖入 room 归类到该 hall 下(major/minor;记录随迁,旧名进别名,预览后确认)" : void 0,
4306
- children: [
4307
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { style: { fontSize: 12, fontWeight: 600 }, children: [
4308
- g.hall,
4309
- g.label ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: " (" + g.label + ")" }) : null
4310
- ] }),
4311
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: `合计 ${g.total} 条 · ${g.entries.length} 个 room` }),
4312
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: S.grow }),
4313
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4314
- "button",
4315
- {
4316
- type: "button",
4317
- disabled: !hiPriv || busy,
4318
- title: `预填注册表单为 ${g.hall}/…(注册该 hall 下的小类)`,
4319
- onClick: () => setRegSlug(g.hall + "/"),
4320
- style: btn,
4321
- children: "+小类"
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;
4322
4358
  }
4323
- )
4324
- ]
4325
- }
4326
- ),
4327
- g.entries.map((e) => {
4328
- const standalone = e.slug === g.hall;
4329
- const count = rooms.find((r) => r.room === e.slug)?.count ?? 0;
4330
- const retired = e.status === "retired";
4331
- 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: [
4332
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
4333
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { flex: 1, minWidth: 0, display: "flex", flexWrap: "wrap", gap: "2px 8px", alignItems: "baseline" }, children: [
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: [
4334
4373
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4335
4374
  "button",
4336
4375
  {
4337
4376
  type: "button",
4338
- title: expandedRooms.has(e.slug) ? "收起内容" : "展开查看该 Room 的记录内容(默认收起)",
4339
- onClick: () => void toggleRoomExpand(e.slug),
4377
+ title: hallOpen ? "收起子 room" : "展开子 room(默认折叠)",
4378
+ onClick: (ev) => {
4379
+ ev.stopPropagation();
4380
+ toggleHall(g.hall);
4381
+ },
4340
4382
  style: { ...btn, borderRadius: 999, padding: "1px 7px", flexShrink: 0 },
4341
- children: expandedRooms.has(e.slug) ? "▾" : "▸"
4383
+ children: hallOpen ? "▾" : "▸"
4342
4384
  }
4343
4385
  ),
4344
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
4345
- "span",
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",
4346
4394
  {
4347
- style: { fontSize: 12, cursor: hiPriv && !busy ? "grab" : "default", touchAction: "none" },
4348
- onPointerDown: (ev) => onDragLabelDown(ev, e.slug),
4349
- onPointerMove: onDragLabelMove,
4350
- onPointerUp: onDragLabelUp,
4351
- title: hiPriv && !busy ? "按住拖到某个 hall 头上归类(major/minor;记录随迁,旧名进别名)" : e.description ? `${e.slug} — ${e.description}` : e.slug,
4352
- children: [
4353
- standalone ? e.slug : "· " + e.slug.slice(g.hall.length + 1),
4354
- e.source === "pre-registered" ? " ⭐" : ""
4355
- ]
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: "+小类"
4356
4404
  }
4357
- ),
4358
- e.label ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: e.label }) : null,
4359
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: count + " 条" }),
4360
- retired ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { ...S.muted, color: "var(--dsh-mem-danger)" }, children: "已退役" }) : null,
4361
- (e.aliases ?? []).length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { ...S.muted, wordBreak: "break-all" }, children: "别名: " + (e.aliases ?? []).join(", ") }) : null
4362
- ] }),
4363
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { display: "flex", gap: 6, flexShrink: 0, alignItems: "center" }, children: [
4364
- !hiPriv ? null : /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
4365
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", disabled: busy, title: "编辑显示名/归类说明", onClick: () => {
4366
- setEditing({ slug: e.slug, label: e.label ?? "", description: e.description ?? "" });
4367
- setForm(null);
4368
- setPreview(null);
4369
- setPicking(null);
4370
- }, style: btn, children: "编辑" }),
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: [
4371
4416
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4372
4417
  "button",
4373
4418
  {
4374
4419
  type: "button",
4375
- disabled: busy,
4376
- title: picking === e.slug ? "再点一次取消挑选;然后点击目标 hall 头完成归类" : "归类到已有 hall 下:点击后选目标 hall(记录随迁,旧名进别名)",
4377
- onClick: () => {
4378
- if (picking === e.slug) {
4379
- setPicking(null);
4380
- return;
4381
- }
4382
- setForm({ action: "rename", from: e.slug, to: "" });
4383
- setPreview(null);
4384
- setPicking(e.slug);
4385
- setMsg(null);
4386
- setError(null);
4387
- },
4388
- style: { ...btn, ...picking === e.slug ? { color: "var(--dsh-mem-accent)", borderColor: "var(--dsh-mem-accent)" } : {} },
4389
- children: picking === e.slug ? "选 hall…" : "归类"
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
+ ]
4390
4438
  }
4391
4439
  ),
4392
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", disabled: busy, title: `改名(= merge 1:1 + 注册表改名)`, onClick: () => {
4393
- setForm({ action: "rename", from: e.slug, to: "" });
4394
- setPreview(null);
4395
- }, style: btn, children: "改名" }),
4396
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", disabled: busy, title: "合并到另一个 Room(dryRun 预览→确认实跑)", onClick: () => {
4397
- setForm({ action: "merge", from: e.slug, to: "" });
4398
- setPreview(null);
4399
- }, style: btn, children: "合并" }),
4400
- /* @__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 ? "恢复" : "退役" }),
4401
- !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
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
4402
4444
  ] }),
4403
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", disabled: busy, title: "导出该 Room 的记录清单 CSV", onClick: () => exportRecords(e.slug), style: btn, children: "导出" })
4404
- ] })
4405
- ] }),
4406
- 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: [
4407
- (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: [
4408
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "dsh-mem-tag dsh-mem-tag-" + it.type, children: TYPE_LABELS[it.type] || it.type }),
4409
- " ",
4410
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: it.updatedAt ? fmtTime(it.updatedAt) : "" }),
4411
- /* @__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 })
4412
- ] }, it.id)),
4413
- (roomRecords[e.slug]?.items.length ?? 0) === 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: S.muted, children: "该 Room 暂无记录。" }) : null,
4414
- (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
4415
- ] }) : null
4416
- ] }, e.slug);
4417
- })
4418
- ] }, g.hall)),
4419
- grownOnly.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { marginTop: 4 }, children: [
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: [
4420
4503
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { type: "button", onClick: () => setGrownOpen(!grownOpen), style: btn, title: grownOpen ? "收起未注册自生长 Room" : "展开未注册自生长 Room", children: [
4421
4504
  grownOpen ? "▾" : "▸",
4422
4505
  " 自生长未注册 · 共 ",
4423
- grownOnly.length
4506
+ visibleGrown.length
4424
4507
  ] }),
4425
4508
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: S.muted, children: "(只在 tags 里涌现,不在词表 —— 收编后标注器才会优先挂靠)" }),
4426
- grownOpen ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, alignItems: "center", marginTop: 6 }, children: grownOnly.map((r) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { style: { display: "inline-flex", alignItems: "center", gap: 4 }, children: [
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: [
4427
4510
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
4428
4511
  "span",
4429
4512
  {
@@ -4498,6 +4581,7 @@ var __defProp = Object.defineProperty;
4498
4581
  const [renaming, setRenaming] = (0, import_react17.useState)(null);
4499
4582
  const [expanded, setExpanded] = (0, import_react17.useState)(/* @__PURE__ */ new Set());
4500
4583
  const [retiredFilter, setRetiredFilter] = (0, import_react17.useState)("");
4584
+ const [filter, setFilter] = (0, import_react17.useState)("");
4501
4585
  const [records, setRecords] = (0, import_react17.useState)({});
4502
4586
  const loadAll = (0, import_react17.useCallback)(() => {
4503
4587
  rpc("dsh-memory/rooms-get", {}).then((r) => {
@@ -4648,6 +4732,10 @@ var __defProp = Object.defineProperty;
4648
4732
  if (retiredFilter === "retired") return row2.entry?.status === "retired";
4649
4733
  return true;
4650
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;
4651
4739
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { children: [
4652
4740
  /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { ...S.flexRow, marginBottom: 8, flexWrap: "wrap" }, children: [
4653
4741
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: S.muted, children: "Room 级管理:存在(注册/收编/退役)· 名称(显示名/说明/改名)· 条目(展开浏览) · 归类到 hall 请到「Hall」页" }),
@@ -4698,7 +4786,7 @@ var __defProp = Object.defineProperty;
4698
4786
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", disabled: busy, onClick: () => setRenaming(null), style: btn2, children: "取消" }),
4699
4787
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: S.muted, children: "记录随迁;与反刍并发会互相覆盖,请在反刍空闲时执行。" })
4700
4788
  ] }) : null,
4701
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 8 }, children: [
4789
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 8, flexWrap: "wrap" }, children: [
4702
4790
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: S.muted, children: "退场筛选" }),
4703
4791
  [["", "全部"], ["active", "活跃"], ["retired", "已退役"]].map(([val, label]) => {
4704
4792
  const on = retiredFilter === val;
@@ -4721,7 +4809,18 @@ var __defProp = Object.defineProperty;
4721
4809
  },
4722
4810
  val
4723
4811
  );
4724
- })
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
4725
4824
  ] }),
4726
4825
  /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, alignItems: "center", marginBottom: 10 }, children: [
4727
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 } }),
@@ -4732,7 +4831,7 @@ var __defProp = Object.defineProperty;
4732
4831
  ] }),
4733
4832
  msg ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: S.hint, children: msg }) : null,
4734
4833
  error ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: S.error, children: error }) : null,
4735
- rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { style: S.intro, children: "暂无 Room:对话/反刍会从标签涌现,或在上方注册。" }) : rows.map((row2) => {
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) => {
4736
4835
  const e = row2.entry;
4737
4836
  const retired = e?.status === "retired";
4738
4837
  const grownOnly = !e;
package/dist/stats.js CHANGED
@@ -548,6 +548,11 @@ export async function handleEndpoint(endpoint, payload, deps) {
548
548
  if (!slug)
549
549
  throw new Error('retire 需要 slug');
550
550
  const active = p.active === true;
551
+ // 自生长未注册 slug 退场:自动补一条 grown 注册条目再置 retired——
552
+ // 否则词表无物可变,面板按钮无法切换为「恢复」(用户实测反馈)
553
+ if (!active && p.withRecords === true && !roomRegistry.bySlug(slug)) {
554
+ await roomRegistry.register({ slug, source: 'grown' });
555
+ }
551
556
  const changed = await roomRegistry.setStatus(slug, active ? 'active' : 'retired');
552
557
  // 带记录退场(软删,可恢复):该 room 下全部**活跃**记录分批 retire(每批 ≤200,与 records-delete 同原语)
553
558
  let retiredRecords = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-prime-memory",
3
- "version": "0.20.0-beta.24",
3
+ "version": "0.20.0-beta.26",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org",
6
6
  "access": "public",