dsh-layered-memory 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.en.md +101 -81
  2. package/README.md +91 -71
  3. package/assets/img/Hero.png +0 -0
  4. package/assets/img/Layers.png +0 -0
  5. package/assets/img/Modes.png +0 -0
  6. package/assets/img/ui-dark.jpg +0 -0
  7. package/assets/img/ui-light.jpg +0 -0
  8. package/assets/readme/flow.svg +189 -0
  9. package/assets/readme/storage.svg +115 -0
  10. package/dist/client.js +1242 -325
  11. package/dist/config.d.ts +12 -0
  12. package/dist/config.js +20 -17
  13. package/dist/hooks/capture.d.ts +17 -1
  14. package/dist/hooks/capture.js +45 -11
  15. package/dist/hooks/recall.d.ts +7 -0
  16. package/dist/hooks/recall.js +20 -4
  17. package/dist/index.d.ts +8 -0
  18. package/dist/index.js +112 -44
  19. package/dist/pipeline/rebuild.d.ts +80 -0
  20. package/dist/pipeline/rebuild.js +307 -0
  21. package/dist/pipeline/runner.d.ts +42 -4
  22. package/dist/pipeline/runner.js +125 -21
  23. package/dist/settings.js +79 -20
  24. package/dist/stats.d.ts +8 -1
  25. package/dist/stats.js +156 -14
  26. package/dist/store/download-queue.d.ts +71 -0
  27. package/dist/store/download-queue.js +313 -0
  28. package/dist/store/embedding-source.d.ts +160 -0
  29. package/dist/store/embedding-source.js +421 -0
  30. package/dist/store/embedding.d.ts +3 -1
  31. package/dist/store/embedding.js +5 -0
  32. package/dist/store/l0.d.ts +13 -5
  33. package/dist/store/l0.js +33 -6
  34. package/dist/store/l1.d.ts +15 -5
  35. package/dist/store/l1.js +40 -15
  36. package/dist/store/local-embedding.d.ts +64 -0
  37. package/dist/store/local-embedding.js +120 -0
  38. package/dist/store/model-catalog.d.ts +45 -0
  39. package/dist/store/model-catalog.js +78 -0
  40. package/dist/store/pending.d.ts +15 -0
  41. package/dist/store/pending.js +56 -0
  42. package/dist/store/runtime-installer.d.ts +60 -0
  43. package/dist/store/runtime-installer.js +181 -0
  44. package/dist/store/sqlite.d.ts +68 -7
  45. package/dist/store/sqlite.js +414 -71
  46. package/dist/store/state.d.ts +6 -0
  47. package/dist/store/state.js +11 -0
  48. package/dist/tools/index.js +6 -4
  49. package/dist/util/filelog.d.ts +2 -0
  50. package/dist/util/filelog.js +20 -3
  51. package/package.json +1 -1
  52. package/assets/readme/hero.svg +0 -58
package/dist/client.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * dsh-layered-memory — browser half(手写 plain-JS bundle,无打包器)。
3
3
  *
4
4
  * 1. 输入栏(conversation.input.left,模式选择器右侧):会话记忆档位 pill
5
- * (关闭/chat/自动/work 四态),点击展开 macOS 风格滑动选择器;
5
+ * (关闭/日常/工作/智能 四态,配置键 off/chat/work/auto),点击展开滑动选择器;
6
6
  * 2. 设置 → 记忆:多 Tab 记忆浏览器(概览+开关 / L1 记忆 / L2 场景 / L3 画像 / 运行日志,
7
7
  * 两族混合视图)。
8
8
  * 数据通道:ctx.connection.rpc.call('/rpc', 'dsh-memory/*', payload) → Host 侧 RPC 端点。
@@ -19,108 +19,111 @@ window.__ModuleLoader__.load({
19
19
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
20
20
  var react = require("react");
21
21
 
22
+ // ── dsh 原生 UI 组件(官方 seed 模块 @deepseek-ai/dsh-client-ui-primitives,
23
+ // 与宿主视觉完全一致)。宿主未注册该模块(老版本 dsh)时静默回退到
24
+ // 本 bundle 的等效实现——degrade-don't-crash,与插件整体降级哲学一致。 ──
25
+ var P = null;
26
+ try {
27
+ P = require("@deepseek-ai/dsh-client-ui-primitives");
28
+ } catch (e) {
29
+ P = null;
30
+ }
31
+
32
+ /** 原生 Button(size sm)优先;回退 .dsh-mem-btn 类按钮(剥离原生专属 props)。 */
33
+ function NButton(props) {
34
+ if (P && P.Button) return react.createElement(P.Button, Object.assign({ size: "sm" }, props));
35
+ var rest = Object.assign({}, props);
36
+ delete rest.variant;
37
+ delete rest.icon;
38
+ rest.className = "dsh-mem-btn" + (rest.className ? " " + rest.className : "");
39
+ return react.createElement("button", rest);
40
+ }
41
+
42
+ /** 原生 Input 优先;回退 .dsh-mem-input 类输入框。
43
+ * 原生 Input 是 span>input 结构且 rest 摊给内层 input——布局属性(flex/minWidth
44
+ * 等)必须路由到外层,否则搜索框在 flex 工具栏里不再撑满。 */
45
+ function NInput(props) {
46
+ if (P && P.Input) {
47
+ var inner = Object.assign({}, props);
48
+ var layoutStyle = inner.style;
49
+ delete inner.style;
50
+ return react.createElement("span", { style: layoutStyle }, react.createElement(P.Input, inner));
51
+ }
52
+ var rest = Object.assign({}, props);
53
+ rest.className = "dsh-mem-input" + (rest.className ? " " + rest.className : "");
54
+ return react.createElement("input", rest);
55
+ }
56
+
57
+ /** 原生 Modal 优先;回退 .dsh-mem-rb-overlay/.dsh-mem-rb-modal 模态。 */
58
+ function NModal(props) {
59
+ if (props.open === false) return null;
60
+ if (P && P.Modal) return react.createElement(P.Modal, Object.assign({ closeLabel: "关闭" }, props));
61
+ return react.createElement(
62
+ "div",
63
+ {
64
+ className: "dsh-mem-rb-overlay",
65
+ onClick: function (e) {
66
+ if (e.target === e.currentTarget && props.onClose) props.onClose();
67
+ },
68
+ },
69
+ react.createElement(
70
+ "div",
71
+ { className: "dsh-mem-rb-modal" },
72
+ props.title
73
+ ? react.createElement("div", { style: { fontSize: 15, fontWeight: 600, marginBottom: 10 } }, props.title)
74
+ : null,
75
+ props.children,
76
+ props.footer
77
+ ? react.createElement(
78
+ "div",
79
+ { style: { display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 16 } },
80
+ props.footer,
81
+ )
82
+ : null,
83
+ ),
84
+ );
85
+ }
86
+
22
87
  var inject = ["slots", "connection"];
23
88
 
24
- // ── 通用样式 ──
89
+ // ── 通用样式:颜色一律走 --dsh-mem-* 令牌(随 Light/Dark 主题自动切换);
90
+ // 视觉与交互态(hover/active/focus)在注入样式表的类里,这里只留布局 ──
25
91
  var S = {
26
92
  section: { padding: "0 4px" },
27
- heading: { fontSize: 15, fontWeight: 600, margin: "0 0 4px" },
28
- intro: { fontSize: 13, color: "var(--dsw-alias-label-tertiary, #888)", margin: "0 0 12px" },
29
- badge: {
30
- display: "inline-block",
31
- padding: "2px 10px",
32
- borderRadius: 10,
33
- fontSize: 12,
34
- fontWeight: 600,
35
- marginLeft: 8,
36
- verticalAlign: "middle",
37
- },
38
- badgeOk: { background: "var(--dsw-alias-interactive-bg-primary, #1a7f37)", color: "#fff" },
39
- badgeErr: { background: "var(--dsw-alias-state-error-primary, #cf222e)", color: "#fff" },
40
- tabbar: { display: "flex", gap: 4, borderBottom: "1px solid var(--dsw-alias-border-secondary, #e0e0e0)", marginBottom: 12 },
41
- tab: {
42
- padding: "6px 14px",
43
- fontSize: 13,
44
- border: "none",
45
- background: "none",
46
- cursor: "pointer",
47
- color: "var(--dsw-alias-label-secondary, #666)",
48
- borderBottom: "2px solid transparent",
49
- marginBottom: -1,
50
- },
51
- tabActive: {
52
- fontWeight: 600,
53
- color: "var(--dsw-alias-label-primary, #111)",
54
- borderBottom: "2px solid var(--dsw-alias-interactive-bg-primary, #1a7f37)",
55
- },
56
- table: {
57
- width: "100%",
58
- borderCollapse: "collapse",
59
- fontSize: 13,
60
- fontFamily: "ui-monospace, SFMono-Regular, Consolas, monospace",
61
- },
62
- td: { padding: "6px 10px", borderBottom: "1px solid var(--dsw-alias-border-secondary, #eee)" },
63
- tdKey: { width: 180, color: "var(--dsw-alias-label-tertiary, #888)", whiteSpace: "nowrap" },
93
+ heading: { fontSize: 16, fontWeight: 600, margin: "0 0 4px", color: "var(--dsh-mem-text-1)" },
94
+ intro: { fontSize: 13, color: "var(--dsh-mem-text-3)", margin: "0 0 12px" },
95
+ tabbar: { display: "flex", gap: 2, borderBottom: "1px solid var(--dsh-mem-border)", marginBottom: 14 },
64
96
  error: {
65
97
  marginTop: 10,
66
98
  fontSize: 13,
67
- color: "var(--dsh-alias-state-error-primary, #cf222e)",
99
+ color: "var(--dsh-mem-danger)",
68
100
  whiteSpace: "pre-wrap",
69
101
  },
70
- hint: { marginTop: 12, fontSize: 12, color: "var(--dsw-alias-label-tertiary, #999)" },
102
+ hint: { marginTop: 12, fontSize: 12, color: "var(--dsh-mem-text-3)" },
71
103
  toolbar: { display: "flex", gap: 8, marginBottom: 12, flexWrap: "wrap", alignItems: "center" },
72
- input: {
73
- flex: 1,
74
- minWidth: 160,
75
- padding: "5px 10px",
76
- fontSize: 13,
77
- borderRadius: 6,
78
- border: "1px solid var(--dsw-alias-border-secondary, #ccc)",
79
- background: "var(--dsw-alias-bg-primary, #fff)",
80
- color: "var(--dsw-alias-label-primary, #111)",
81
- },
82
- select: { padding: "5px 6px", fontSize: 13, borderRadius: 6, border: "1px solid var(--dsw-alias-border-secondary, #ccc)" },
83
- button: {
84
- padding: "5px 14px",
85
- fontSize: 13,
86
- borderRadius: 6,
87
- border: "1px solid var(--dsw-alias-border-secondary, #ccc)",
88
- background: "var(--dsw-alias-bg-primary, #fff)",
89
- cursor: "pointer",
90
- },
91
104
  card: {
92
- border: "1px solid var(--dsw-alias-border-secondary, #e5e5e5)",
93
- borderRadius: 8,
94
105
  padding: "10px 12px",
95
106
  marginBottom: 8,
96
107
  fontSize: 13,
97
108
  },
98
109
  cardHead: { display: "flex", alignItems: "center", gap: 8, marginBottom: 4 },
99
- tag: {
100
- display: "inline-block",
101
- padding: "1px 8px",
102
- borderRadius: 8,
103
- fontSize: 11,
104
- fontWeight: 600,
105
- color: "#fff",
106
- whiteSpace: "nowrap",
107
- },
108
- muted: { color: "var(--dsw-alias-label-tertiary, #999)", fontSize: 12 },
109
- content: { lineHeight: 1.5, wordBreak: "break-word" },
110
+ muted: { color: "var(--dsh-mem-text-3)", fontSize: 12 },
111
+ content: { lineHeight: 1.5, wordBreak: "break-word", color: "var(--dsh-mem-text-1)" },
110
112
  detail: {
111
113
  marginTop: 8,
112
114
  paddingTop: 8,
113
- borderTop: "1px dashed var(--dsw-alias-border-secondary, #ddd)",
115
+ borderTop: "1px dashed var(--dsh-mem-border)",
114
116
  fontSize: 12,
115
117
  fontFamily: "ui-monospace, SFMono-Regular, Consolas, monospace",
116
- color: "var(--dsw-alias-label-secondary, #666)",
118
+ color: "var(--dsh-mem-text-2)",
117
119
  whiteSpace: "pre-wrap",
118
120
  },
119
121
  pre: {
120
122
  margin: 0,
121
123
  padding: "10px 12px",
122
- background: "var(--dsw-alias-bg-secondary, #f6f8fa)",
123
- borderRadius: 8,
124
+ background: "var(--dsh-mem-bg-inset)",
125
+ border: "1px solid var(--dsh-mem-border)",
126
+ borderRadius: 10,
124
127
  fontSize: 12,
125
128
  fontFamily: "ui-monospace, SFMono-Regular, Consolas, monospace",
126
129
  whiteSpace: "pre-wrap",
@@ -130,19 +133,19 @@ window.__ModuleLoader__.load({
130
133
  overflow: "auto",
131
134
  },
132
135
  switchRow: { display: "flex", alignItems: "center", gap: 10, padding: "8px 0" },
133
- switchLabel: { fontSize: 13, fontWeight: 600, minWidth: 72 },
134
- switchDesc: { fontSize: 12, color: "var(--dsw-alias-label-tertiary, #999)" },
136
+ switchLabel: { fontSize: 13, fontWeight: 600, minWidth: 72, color: "var(--dsh-mem-text-1)" },
137
+ switchDesc: { fontSize: 12, color: "var(--dsh-mem-text-3)" },
135
138
  switch: {
136
139
  width: 36,
137
140
  height: 20,
138
- borderRadius: 10,
141
+ borderRadius: 999,
139
142
  position: "relative",
140
143
  cursor: "pointer",
141
144
  transition: "background .15s",
142
145
  flexShrink: 0,
143
146
  },
144
- switchOn: { background: "var(--dsw-alias-interactive-bg-primary, #1a7f37)" },
145
- switchOff: { background: "var(--dsw-alias-border-secondary, #ccc)" },
147
+ switchOn: { background: "var(--dsh-mem-accent-fill)" },
148
+ switchOff: { background: "var(--dsh-mem-border-strong)" },
146
149
  switchDisabled: { opacity: 0.4, cursor: "not-allowed" },
147
150
  knob: {
148
151
  position: "absolute",
@@ -150,22 +153,56 @@ window.__ModuleLoader__.load({
150
153
  width: 16,
151
154
  height: 16,
152
155
  borderRadius: "50%",
153
- background: "#fff",
156
+ background: "var(--dsh-mem-thumb)",
154
157
  transition: "left .15s",
155
- boxShadow: "0 1px 2px rgba(0,0,0,.2)",
158
+ boxShadow: "0 1px 2px rgba(0,0,0,.25)",
156
159
  },
157
160
  switchPanel: {
158
- border: "1px solid var(--dsw-alias-border-secondary, #e5e5e5)",
159
- borderRadius: 8,
160
- padding: "4px 12px",
161
+ border: "1px solid var(--dsh-mem-border)",
162
+ borderRadius: 10,
163
+ background: "var(--dsh-mem-bg-card)",
164
+ boxShadow: "var(--dsh-mem-shadow-card)",
165
+ padding: "4px 14px",
161
166
  marginBottom: 14,
162
167
  },
168
+ panelLabel: {
169
+ fontSize: 12,
170
+ fontWeight: 600,
171
+ color: "var(--dsh-mem-text-3)",
172
+ margin: "12px 0 2px",
173
+ },
174
+ statGrid: {
175
+ display: "grid",
176
+ gridTemplateColumns: "repeat(auto-fill, minmax(140px, 1fr))",
177
+ gap: 8,
178
+ marginBottom: 14,
179
+ },
180
+ statTile: { padding: "10px 12px" },
181
+ statNum: { fontSize: 20, fontWeight: 650, lineHeight: "28px", color: "var(--dsh-mem-text-1)" },
182
+ statLabel: { fontSize: 12, color: "var(--dsh-mem-text-3)", marginTop: 2 },
183
+ infoRow: {
184
+ display: "flex",
185
+ alignItems: "baseline",
186
+ gap: 12,
187
+ padding: "5px 0",
188
+ borderBottom: "1px solid var(--dsh-mem-border)",
189
+ },
190
+ infoKey: { fontSize: 12.5, color: "var(--dsh-mem-text-3)", whiteSpace: "nowrap", minWidth: 96 },
191
+ infoVal: {
192
+ fontSize: 12.5,
193
+ color: "var(--dsh-mem-text-1)",
194
+ fontFamily: "ui-monospace, SFMono-Regular, Consolas, monospace",
195
+ wordBreak: "break-all",
196
+ textAlign: "right",
197
+ flex: 1,
198
+ },
163
199
  seg: {
164
200
  display: "inline-flex",
165
- border: "1px solid var(--dsw-alias-border-secondary, #e5e5e5)",
201
+ border: "1px solid var(--dsh-mem-border)",
166
202
  borderRadius: 8,
167
203
  overflow: "hidden",
168
204
  flexShrink: 0,
205
+ background: "var(--dsh-mem-bg-inset)",
169
206
  },
170
207
  segBtn: {
171
208
  padding: "4px 12px",
@@ -173,33 +210,21 @@ window.__ModuleLoader__.load({
173
210
  lineHeight: "16px",
174
211
  cursor: "pointer",
175
212
  background: "transparent",
176
- color: "inherit",
213
+ color: "var(--dsh-mem-text-2)",
177
214
  border: "none",
178
- borderRight: "1px solid var(--dsw-alias-border-secondary, #e5e5e5)",
215
+ borderRight: "1px solid var(--dsh-mem-border)",
179
216
  },
180
217
  segBtnOn: {
181
- background: "var(--dsw-alias-interactive-bg-primary, #1a7f37)",
218
+ background: "var(--dsh-mem-accent-fill)",
182
219
  color: "#fff",
183
220
  fontWeight: 600,
184
221
  },
185
- segBtnOff: {
186
- background: "var(--dsw-alias-bg-secondary, #f6f8fa)",
187
- },
188
222
  flexRow: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" },
189
223
  grow: { flex: 1 },
190
224
  sceneHead: { display: "flex", alignItems: "baseline", gap: 10, marginBottom: 6, flexWrap: "wrap" },
191
- sceneTitle: { fontSize: 13, fontWeight: 600, fontFamily: "ui-monospace, Consolas, monospace" },
225
+ sceneTitle: { fontSize: 13, fontWeight: 600, fontFamily: "ui-monospace, Consolas, monospace", color: "var(--dsh-mem-text-1)" },
192
226
  };
193
227
 
194
- var TYPE_COLORS = {
195
- persona: "#8250df",
196
- episodic: "#0969da",
197
- instruction: "#bf8700",
198
- work_fact: "#0969da",
199
- work_task: "#1a7f37",
200
- work_method: "#8250df",
201
- work_artifact: "#bf8700",
202
- };
203
228
  var TYPE_LABELS = {
204
229
  persona: "画像偏好",
205
230
  episodic: "客观事件",
@@ -211,7 +236,7 @@ window.__ModuleLoader__.load({
211
236
  };
212
237
 
213
238
  function fmtTime(iso) {
214
- if (!iso) return "";
239
+ if (!iso) return "-";
215
240
  try {
216
241
  return new Date(iso).toLocaleString();
217
242
  } catch (e) {
@@ -253,7 +278,7 @@ window.__ModuleLoader__.load({
253
278
  );
254
279
  }
255
280
 
256
- // ── Segmented 组件:分段选择器(记忆设置页的蒸馏思考档位等单选项) ──
281
+ // ── Segmented 组件:分段选择器(支持逐项禁用:远程档未配四件套时置灰) ──
257
282
  function Segmented(props) {
258
283
  var value = props.value;
259
284
  var disabled = !!props.disabled;
@@ -262,19 +287,21 @@ window.__ModuleLoader__.load({
262
287
  { style: Object.assign({}, S.seg, disabled ? S.switchDisabled : null) },
263
288
  props.options.map(function (opt, i) {
264
289
  var on = opt.key === value;
290
+ var optDisabled = disabled || !!opt.disabled;
265
291
  return react.createElement(
266
292
  "span",
267
293
  {
268
294
  key: opt.key,
295
+ title: opt.disabledTitle || "",
269
296
  style: Object.assign(
270
297
  {},
271
298
  S.segBtn,
272
- on ? S.segBtnOn : S.segBtnOff,
299
+ on ? S.segBtnOn : null,
273
300
  i === props.options.length - 1 ? { borderRight: "none" } : null,
274
- disabled ? { cursor: "not-allowed" } : null,
301
+ optDisabled ? { cursor: "not-allowed", opacity: 0.45 } : null,
275
302
  ),
276
303
  onClick: function () {
277
- if (!disabled && !on && props.onChange) props.onChange(opt.key);
304
+ if (!optDisabled && !on && props.onChange) props.onChange(opt.key);
278
305
  },
279
306
  },
280
307
  opt.label,
@@ -283,17 +310,36 @@ window.__ModuleLoader__.load({
283
310
  );
284
311
  }
285
312
 
286
- // ── 会话记忆档位控件(输入栏 pill + macOS 风格滑动选择器) ──
287
- // 档位顺序即滑轨顺序:关闭 → chatwork自动(默认档"自动"居右)
313
+ // ── 会话记忆档位控件(输入栏 pill + 滑动选择器) ──
314
+ // 档位顺序即滑轨顺序:关闭 → 日常工作智能(默认档"智能"居右)。
315
+ // 显示名中文;配置键与英文层保持 off/chat/work/auto(session-modes.json 不变)。
316
+ // 档位色是 CSS 变量引用(--dsh-mem-mode-*,Light/Dark 各一组值,注入样式表定义),
317
+ // 取值 = 灰 → 品牌蓝 的渐变阶:off 灰、chat/work 过渡蓝、auto 品牌蓝。
288
318
  var MODES = [
289
- { key: "off", label: "关闭", color: "#6e7781" },
290
- { key: "chat", label: "chat", color: "#1a7f37" },
291
- { key: "work", label: "work", color: "#bf8700" },
292
- { key: "auto", label: "自动", color: "#0969da" },
319
+ { key: "off", label: "关闭", color: "var(--dsh-mem-text-2)" },
320
+ { key: "chat", label: "日常", color: "var(--dsh-mem-mode-chat)" },
321
+ { key: "work", label: "工作", color: "var(--dsh-mem-mode-work)" },
322
+ { key: "auto", label: "智能", color: "var(--dsh-mem-mode-auto)" },
293
323
  ];
294
324
  var TRACK_W = 200;
295
325
  var THUMB = 16;
326
+ var RAIL_H = 22; // 粗滑轨高度 > 圆球直径(圆球被滑轨包裹)
296
327
  var INNER_W = TRACK_W - THUMB;
328
+ // 点阵粒子场档位参数(参考 DSH-Claude-Style-Reasoning-Slider 的分档场强,
329
+ // 配色锁品牌蓝单色系):density 越大点阵越密、alpha 亮度系数、wave 明暗水波纹、
330
+ // tempo 闪烁节拍倍率。tier0(关闭)不参与——show=false 整层不画
331
+ var FIELD_TIERS = [
332
+ { density: 0, alpha: 0, wave: 0, tempo: 1 },
333
+ { density: 0.34, alpha: 0.5, wave: 0, tempo: 1 }, // 日常:稀疏微光
334
+ { density: 0.55, alpha: 0.78, wave: 1, tempo: 1.15 }, // 工作:中强 + 水波纹
335
+ { density: 0.72, alpha: 1, wave: 1, tempo: 1.3 }, // 智能:满场最活跃
336
+ ];
337
+
338
+ /** smoothstep(粒子场展开/揭示用 ease) */
339
+ function smStep(a, b, x) {
340
+ var t = Math.min(1, Math.max(0, (x - a) / (b - a)));
341
+ return t * t * (3 - 2 * t);
342
+ }
297
343
 
298
344
  function modeInfo(key) {
299
345
  for (var i = 0; i < MODES.length; i++) if (MODES[i].key === key) return MODES[i];
@@ -301,10 +347,10 @@ window.__ModuleLoader__.load({
301
347
  }
302
348
 
303
349
  function modeLabel(key) {
304
- if (key === "auto") return "自动(双族)";
305
- if (key === "chat") return "chat(个人)";
306
- if (key === "work") return "work(工作)";
307
- return "off(关闭)";
350
+ if (key === "auto") return "智能(双族)";
351
+ if (key === "chat") return "日常(个人)";
352
+ if (key === "work") return "工作(团队)";
353
+ return "关闭";
308
354
  }
309
355
 
310
356
  function modeIndex(key) {
@@ -314,12 +360,15 @@ window.__ModuleLoader__.load({
314
360
 
315
361
  /** 滑动选择器浮层(参考 macOS 滑动器:拖拽圆头 1:1 连续跟手,松手按动量投影吸附最近档)。 */
316
362
  function ModeSlider(props) {
317
- ensureFlowStyle(); // 玻璃材质 class 与流光共用同一张注入样式表
363
+ ensureThemeStyle(); // 主题令牌与浮层/气泡 class 共用同一张注入样式表
318
364
  var trackRef = react.useRef(null);
319
365
  // 拖拽状态:{ x: 圆头连续位置 px, lastX: 上次指针 clientX, t: 时间戳, v: 速度 px/ms(EMA 平滑) }
320
366
  var dragState = react.useState(null);
321
367
  var drag = dragState[0];
322
368
  var setDrag = dragState[1];
369
+ // 粒子层 canvas ref + 几何快照 ref(rAF 循环跨帧读最新值,不重建 effect)
370
+ var canvasRef = react.useRef(null);
371
+ var geoRef = react.useRef(null);
323
372
 
324
373
  var clampX = function (x) {
325
374
  if (x < 0) return 0;
@@ -362,77 +411,180 @@ window.__ModuleLoader__.load({
362
411
  var activeIdx = Math.min(MODES.length - 1, Math.max(0, Math.round((thumbLeft / INNER_W) * (MODES.length - 1))));
363
412
  var info = MODES[activeIdx];
364
413
 
414
+ // 粒子层几何快照:每帧渲染时更新,rAF 循环跨帧读取(避免按帧重建 effect)
415
+ geoRef.current = {
416
+ origin: thumbLeft + THUMB / 2, // 密度/亮度中心 = 圆球中心
417
+ rightEdge: thumbLeft + THUMB, // 粒子活动区右界 = 填充右缘(不越过圆球)
418
+ tier: activeIdx, // 场强档位(与填充/气泡同源;拖拽预览即时升降级)
419
+ show: activeIdx > 0 || drag !== null, // 与填充显隐同源
420
+ dragging: drag !== null,
421
+ };
422
+
423
+ // 粒子层动画循环:DPR 适配 + ResizeObserver + 主题观察;reduced-motion 只画静帧。
424
+ // 依赖数组为空——几何/拖拽态经 geoRef 传递,effect 全生命周期只建一次
425
+ react.useEffect(function () {
426
+ var canvas = canvasRef.current;
427
+ if (!canvas) return undefined;
428
+ var ctx = canvas.getContext && canvas.getContext("2d");
429
+ if (!ctx) return undefined;
430
+ var reduced = window.matchMedia("(prefers-reduced-motion: reduce)");
431
+ var width = 1;
432
+ var height = 1;
433
+ var frame = 0;
434
+ var grid = []; // 点阵网格(resize 时预计算静态哈希,逐帧只做时间维运算)
435
+ var cell = 5;
436
+ var gap = 1.1;
437
+ var fieldOn = false; // show 翻转沿:入场展开动画的计时原点
438
+ var fieldStart = 0;
439
+ var lastDrawn = 0;
440
+
441
+ var resize = function () {
442
+ var b = canvas.getBoundingClientRect();
443
+ var ratio = Math.min(window.devicePixelRatio || 1, 2);
444
+ width = Math.max(1, b.width);
445
+ height = Math.max(1, b.height);
446
+ canvas.width = Math.max(1, Math.round(width * ratio));
447
+ canvas.height = Math.max(1, Math.round(height * ratio));
448
+ ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
449
+ // 网格:200×22 轨道 → 40 列 × 5 行 ≈ 200 格(哈希来自仓库B 的预计算方案)
450
+ cell = width < 280 ? 5 : 6;
451
+ grid = [];
452
+ for (var row = 0; row * cell < height; row++) {
453
+ for (var column = 0; column * cell < width; column++) {
454
+ grid.push({
455
+ x: column * cell,
456
+ y: row * cell,
457
+ base: Math.abs(Math.sin(column * 12.9898 + row * 78.233) * 43758.5453) % 1,
458
+ tempo: Math.abs(Math.sin(column * 7.13 + row * 19.41) * 19341.731) % 1,
459
+ phase: Math.abs(Math.sin(column * 31.17 + row * 11.93) * 28437.123) % 1,
460
+ });
461
+ }
462
+ }
463
+ };
464
+
465
+ var draw = function (time) {
466
+ var st = geoRef.current || { origin: 0, rightEdge: 0, tier: 0, show: false, dragging: false };
467
+ ctx.clearRect(0, 0, width, height);
468
+ if (!st.show || st.rightEdge <= 0) {
469
+ fieldOn = false;
470
+ return;
471
+ }
472
+ if (!fieldOn) {
473
+ fieldOn = true;
474
+ fieldStart = time; // 入场展开从这一刻起算(900ms 从圆球向外揭示)
475
+ }
476
+ var dark = document.body.hasAttribute("data-ds-dark-theme");
477
+ var tier = FIELD_TIERS[st.tier] || FIELD_TIERS[1];
478
+ var elapsed = Math.max(0, time - fieldStart);
479
+ var reveal = reduced.matches ? 1 : smStep(0, 1, elapsed / 900);
480
+ var ripplePhase = (elapsed % 1200) / 1200; // 明暗水波纹(1200ms 一轮,从球向外)
481
+ var tempo = tier.tempo * (st.dragging ? 2 : 1); // 拖拽全档提速
482
+ // 基色→高亮色:浅色用深蓝(multiply 混合下沉显色)/ 暗色用亮蓝
483
+ var dim = dark ? [124, 144, 250] : [61, 91, 224];
484
+ var hot = dark ? [214, 224, 255] : [126, 148, 250];
485
+
486
+ ctx.save();
487
+ ctx.beginPath();
488
+ // 裁剪到填充区(胶囊形,与滑轨同圆角——矩形裁剪会在圆角末端溢出)
489
+ if (ctx.roundRect) ctx.roundRect(0, 0, st.rightEdge, height, height / 2);
490
+ else ctx.rect(0, 0, st.rightEdge, height);
491
+ ctx.clip();
492
+
493
+ for (var i = 0; i < grid.length; i++) {
494
+ var c = grid[i];
495
+ var dx = Math.abs(c.x + cell * 0.5 - st.origin) / Math.max(1, st.rightEdge * 0.5);
496
+ if (dx > 1) continue;
497
+ var near = Math.min(1, Math.max(0, 1 - dx * 1.1)); // 近球更密更亮
498
+ if (c.base > tier.density - near * 0.3) continue; // 密度门(近球放行更多格)
499
+ // 独立随机闪烁:每格按自身 tempo/phase 起伏
500
+ var flicker = 0.5 + 0.5 * Math.sin(elapsed * 0.012 * tempo + c.tempo * 6.283 + c.phase * 6.283);
501
+ // 明暗水波纹:从球心向外传播的亮带(tier 未开波纹时给常量底)
502
+ var wave = tier.wave ? 0.5 + 0.5 * Math.sin((dx * 2 - ripplePhase) * 6.283) : 0.62;
503
+ // 展开:越靠近球越早亮,向外渐显
504
+ var revealA = smStep(0, 1, reveal * (1 - dx * 0.85) + dx * 0.15);
505
+ var alpha = Math.min(1, (0.26 + 0.44 * flicker + near * 0.28) * (0.28 + 0.72 * wave) * revealA * tier.alpha);
506
+ if (alpha < 0.02) continue;
507
+ // 亮闪格向高亮色靠(flicker×wave 双高才发白)
508
+ var glowMix = Math.max(0, flicker * wave - 0.45) * 1.6;
509
+ ctx.fillStyle = "rgba(" +
510
+ Math.round(dim[0] + (hot[0] - dim[0]) * glowMix) + "," +
511
+ Math.round(dim[1] + (hot[1] - dim[1]) * glowMix) + "," +
512
+ Math.round(dim[2] + (hot[2] - dim[2]) * glowMix) + "," +
513
+ alpha.toFixed(3) + ")";
514
+ ctx.fillRect(c.x + gap * 0.5, c.y + gap * 0.5, cell - gap, cell - gap);
515
+ }
516
+ ctx.restore();
517
+ };
518
+
519
+ var loop = function (time) {
520
+ // 33ms 节流(≈30fps):闪烁/波纹尺度下无可感差异,省电
521
+ if (time - lastDrawn >= 33) {
522
+ lastDrawn = time;
523
+ draw(time);
524
+ }
525
+ frame = window.requestAnimationFrame(loop);
526
+ };
527
+ var redrawStatic = function () {
528
+ if (reduced.matches) draw(performance.now());
529
+ };
530
+ var ro = new ResizeObserver(function () {
531
+ resize();
532
+ redrawStatic();
533
+ });
534
+ var themeObs = new MutationObserver(function () {
535
+ redrawStatic(); // 静帧模式下主题翻转要重画(动画循环每帧自读主题)
536
+ });
537
+ ro.observe(canvas);
538
+ themeObs.observe(document.body, { attributes: true, attributeFilter: ["data-ds-dark-theme"] });
539
+ resize();
540
+ draw(performance.now());
541
+ if (!reduced.matches) frame = window.requestAnimationFrame(loop);
542
+ return function () {
543
+ window.cancelAnimationFrame(frame);
544
+ ro.disconnect();
545
+ themeObs.disconnect();
546
+ };
547
+ }, []);
548
+
549
+ // 停点刻度:轨道上的 4 个小点提示可吸附位置;档位名改由拖动气泡显示
365
550
  var stops = [];
366
551
  for (var i = 0; i < MODES.length; i++) {
367
- (function (i) {
368
- var stopLeft = (i / (MODES.length - 1)) * INNER_W + THUMB / 2;
369
- var active = i === activeIdx;
370
- stops.push(
371
- react.createElement(
372
- "div",
373
- {
374
- key: "stop" + i,
375
- style: {
376
- position: "absolute",
377
- left: stopLeft - 3,
378
- top: 6,
379
- width: 6,
380
- height: 6,
381
- borderRadius: "50%",
382
- background: active ? MODES[i].color : "rgba(128,140,150,0.55)",
383
- zIndex: 2,
384
- },
385
- },
386
- ),
387
- react.createElement(
388
- "button",
389
- {
390
- key: "label" + i,
391
- style: {
392
- position: "absolute",
393
- left: stopLeft,
394
- top: 26,
395
- transform: "translateX(-50%)",
396
- fontSize: 11,
397
- lineHeight: "16px",
398
- padding: "0 4px",
399
- border: "none",
400
- background: "none",
401
- cursor: "pointer",
402
- whiteSpace: "nowrap",
403
- fontWeight: active ? 600 : 400,
404
- // 未激活标签走主题 caption 令牌(浅色 #400 灰蓝 / 暗色 #600),玻璃面上保持可读
405
- color: active ? MODES[i].color : "var(--dsw-alias-label-caption, #888)",
406
- },
407
- onClick: function () { if (drag === null) props.onCommit(MODES[i].key); },
408
- },
409
- MODES[i].label,
410
- ),
411
- );
412
- })(i);
552
+ var stopLeft = (i / (MODES.length - 1)) * INNER_W + THUMB / 2;
553
+ stops.push(
554
+ react.createElement("div", {
555
+ key: "stop" + i,
556
+ style: {
557
+ position: "absolute",
558
+ left: stopLeft - 3,
559
+ top: (RAIL_H - 6) / 2,
560
+ width: 6,
561
+ height: 6,
562
+ borderRadius: "50%",
563
+ background: "var(--dsh-mem-dot)",
564
+ zIndex: 2,
565
+ pointerEvents: "none",
566
+ },
567
+ }),
568
+ );
413
569
  }
414
570
 
415
571
  return react.createElement(
416
572
  "div",
417
573
  {
418
- // 外壳只负责定位(带 transform 居中);玻璃材质拆到内层独立元素——
419
- // Chromium 中 transform 元素上的 backdrop-filter 采样异常,玻璃会失效
574
+ // 外壳只负责定位(带 transform 居中悬浮在按钮上方,水平中轴对齐 pill 中心)
420
575
  style: {
421
576
  position: "absolute",
422
577
  bottom: "calc(100% + 8px)",
423
- // 居中悬浮在按钮上方(水平中轴对齐 pill 中心)
424
578
  left: "50%",
425
579
  transform: "translateX(-50%)",
426
580
  zIndex: 1000,
427
581
  },
428
582
  },
429
- react.createElement("div", {
430
- className: "dsh-mem-glass",
431
- style: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, pointerEvents: "none" },
432
- }),
433
583
  react.createElement(
434
584
  "div",
435
- { style: { position: "relative", padding: "12px 16px 30px" } },
585
+ // dsh 原生菜单同配方浮层:不透明实底(dsw-specific-menu)+ inverted 描边 + lv3 阴影;
586
+ // 上下内边距对称(滑轨垂直居中、浮层紧凑),拖动气泡经 overflow: visible 溢出到浮层上方
587
+ { className: "dsh-mem-popover", style: { position: "relative", padding: "14px 16px" } },
436
588
  react.createElement(
437
589
  "div",
438
590
  {
@@ -441,112 +593,304 @@ window.__ModuleLoader__.load({
441
593
  position: "relative",
442
594
  // 容器宽 = thumb 活动范围(0..INNER_W + THUMB),点击映射与视觉两端严格对齐
443
595
  width: TRACK_W,
444
- height: 16,
596
+ height: RAIL_H,
597
+ borderRadius: 999,
598
+ background: "var(--dsh-mem-track)",
445
599
  touchAction: "none",
446
600
  cursor: drag === null ? "pointer" : "grabbing",
601
+ },
602
+ onPointerDown: onPointerDown,
603
+ onPointerMove: onPointerMove,
604
+ onPointerUp: onPointerUp,
605
+ onPointerCancel: onPointerUp,
447
606
  },
448
- onPointerDown: onPointerDown,
449
- onPointerMove: onPointerMove,
450
- onPointerUp: onPointerUp,
451
- onPointerCancel: onPointerUp,
452
- },
453
- // 线条(底层):只连首尾停点的中心,两端不再露出停点外;
454
- // thumb(当前档定位球)压最上层,不再被线条与停点穿过。
455
- // 中性半透明灰在玻璃材质上深浅主题都可辨
456
- react.createElement("div", {
457
- style: {
458
- position: "absolute",
459
- left: THUMB / 2,
460
- width: INNER_W,
461
- top: 7,
462
- height: 4,
463
- borderRadius: 999,
464
- background: "rgba(128,140,150,0.32)",
465
- pointerEvents: "none",
466
- zIndex: 1,
467
- },
468
- }),
469
- stops,
470
- react.createElement("div", {
471
- style: {
472
- position: "absolute",
473
- left: thumbLeft,
474
- top: 1,
475
- width: THUMB,
476
- height: THUMB,
477
- borderRadius: "50%",
478
- // 微透白 + 上缘高光:与浮层玻璃材质同语言
479
- background: "rgba(255,255,255,0.94)",
480
- border: "1px solid " + info.color,
481
- boxShadow: "0 1px 4px rgba(0,0,0,0.28), inset 0 1px 0 rgba(255,255,255,0.9)",
482
- pointerEvents: "none",
483
- transition: drag === null ? "left 120ms ease" : "none",
484
- zIndex: 3,
485
- },
486
- }),
607
+ // 填充:从滑轨左端铺到圆球右缘(width = thumbLeft + THUMB,整球落在
608
+ // 填充末端上与其重合,无空隙不割裂;auto 档恰好全轨蓝、不超出轨道);
609
+ // 颜色从左往右渐变:左侧浅(fill-1)到球侧深(fill-2);
610
+ // 显隐分两支:静态关闭档(off 且未拖拽)不渲染;拖拽中无论预览到哪档
611
+ // 恒显示(松手落 off 才随提交消失);
612
+ // 松手吸附时 width 与圆球 left 同走 120ms ease(防球与填充瞬时分家)
613
+ activeIdx > 0 || drag !== null
614
+ ? react.createElement("div", {
615
+ style: {
616
+ position: "absolute",
617
+ left: 0,
618
+ top: 0,
619
+ bottom: 0,
620
+ width: thumbLeft + THUMB,
621
+ borderRadius: 999,
622
+ background: "linear-gradient(90deg, var(--dsh-mem-fill-1), var(--dsh-mem-fill-2))",
623
+ pointerEvents: "none",
624
+ zIndex: 1,
625
+ transition: drag === null ? "width 120ms ease" : "none",
626
+ },
627
+ })
628
+ : null,
629
+ stops,
630
+ // 粒子层:点阵粒子场(pointerEvents none 不挡拖拽;拖拽时滤镜增饱和提亮)
631
+ react.createElement("canvas", {
632
+ ref: canvasRef,
633
+ className: "dsh-mem-particles",
634
+ style: {
635
+ position: "absolute",
636
+ left: 0,
637
+ top: 0,
638
+ width: "100%",
639
+ height: "100%",
640
+ pointerEvents: "none",
641
+ zIndex: 2,
642
+ filter: drag !== null ? "saturate(1.45) brightness(1.28) contrast(1.06)" : "none",
643
+ },
644
+ }),
645
+ // 圆球:被粗滑轨包裹(RAIL_H > THUMB),品牌蓝描边,拖拽时阴影加重
646
+ react.createElement("div", {
647
+ style: {
648
+ position: "absolute",
649
+ left: thumbLeft,
650
+ top: (RAIL_H - THUMB) / 2,
651
+ width: THUMB,
652
+ height: THUMB,
653
+ borderRadius: "50%",
654
+ background: "var(--dsh-mem-thumb)",
655
+ border: "1px solid var(--dsh-mem-accent)",
656
+ boxShadow: drag !== null
657
+ ? "0 2px 8px rgba(0,0,0,0.35)"
658
+ : "0 1px 4px rgba(0,0,0,0.25)",
659
+ pointerEvents: "none",
660
+ transition: drag === null ? "left 120ms ease" : "none",
661
+ zIndex: 3,
662
+ },
663
+ }),
664
+ // 拖动气泡:仅拖拽期间显示当前档位名,下尖角指向圆球,松手即消失
665
+ drag !== null
666
+ ? react.createElement(
667
+ "div",
668
+ { className: "dsh-mem-bubble", style: { left: thumbLeft + THUMB / 2, zIndex: 4 } },
669
+ info.label,
670
+ )
671
+ : null,
487
672
  ),
488
673
  props.error
489
- ? react.createElement("div", { style: { fontSize: 11, color: "#cf222e", marginTop: 20, whiteSpace: "nowrap" } }, props.error)
674
+ ? react.createElement("div", { style: { fontSize: 11, color: "var(--dsh-mem-danger)", marginTop: 10, whiteSpace: "nowrap" } }, props.error)
490
675
  : null,
491
676
  ),
492
677
  );
493
678
  }
494
679
 
495
- // ── auto 档边缘流光 + 浮层玻璃材质:inline style 放不了 @keyframes/@property/媒体查询,
496
- // 惰性注入一次性样式表(id 防重复)。dsw 主题变量参考其前端令牌:暗色走 body[data-ds-dark-theme]。 ──
497
- var FLOW_STYLE_ID = "dsh-mem-flow-style";
498
- function ensureFlowStyle() {
499
- if (document.getElementById(FLOW_STYLE_ID)) return;
680
+ // ── 主题令牌层 + 组件样式:inline style 放不了 @keyframes/@property/伪类/媒体查询,
681
+ // 惰性注入一次性样式表(id 防重复)。
682
+ // 主题机制:dsh 前端在 body[data-ds-dark-theme] 切暗色并重定义 --dsw-alias-* 令牌;
683
+ // 我们的中性色链真实 dsw 令牌(缺省时用自带 fallback),强调色是 DeepSeek 品牌蓝体系:
684
+ // accent(图形:下划线/边框/光晕,非文字,3:1 即可)
685
+ // accent-text(表面上的强调文字,双主题 ≥4.5:1)
686
+ // accent-fill(实底填充 + 白字,双主题 ≥4.5:1)
687
+ // 设置页挂载即注入(ensureThemeStyle),输入栏 pill / 浮层 / 重建面板共用。 ──
688
+ var THEME_STYLE_ID = "dsh-mem-theme-style";
689
+ function ensureThemeStyle() {
690
+ if (document.getElementById(THEME_STYLE_ID)) return;
500
691
  var el = document.createElement("style");
501
- el.id = FLOW_STYLE_ID;
692
+ el.id = THEME_STYLE_ID;
502
693
  el.textContent = [
503
- // conic 角度动画需要 @property 注册才能插值;不支持的浏览器优雅降级为静态渐变边框
694
+ // conic 角度动画需要 @property 注册才能插值;不支持 @property 的浏览器里
695
+ // var(--dsh-mem-angle) 无定义 → conic 层失效 → 整条 background 退化为无背景
696
+ // (光带边框与内底一并消失,仅剩文字色)。2026 常青浏览器均已支持,仅作记录。
504
697
  "@property --dsh-mem-angle { syntax: '<angle>'; initial-value: 0deg; inherits: false; }",
505
698
  "@keyframes dshMemFlow { to { --dsh-mem-angle: 360deg; } }",
506
- // 边缘流光(双层背景):border 区画旋转 conic 冷蓝光带;内部必须是【不透明】底色盖住
507
- // 光带(半透明内层会让 conic 透进按钮内部,文字被光斑干扰——实测事故)。
508
- // 不透明底 = 主题底混 12% 冷蓝(color-mix 出来 alpha=1),静态、随主题
699
+ // ── 令牌(浅色)。中性色链【真实存在的】dsw 令牌(design-platform.css 校对):
700
+ // bg-layer-2/3、bg-overlay、border-l1/l2/l3、border-inverted、label-*、
701
+ // interactive-bg-hover、state-error-primary、tooltip-bg、dsw-shadow-lv1/lv3 ──
702
+ ":root {",
703
+ " --dsh-mem-accent: #4d6bfe;",
704
+ " --dsh-mem-accent-text: #3d5be0;",
705
+ " --dsh-mem-accent-fill: #3d5be0;",
706
+ " --dsh-mem-accent-weak: rgba(77,107,254,0.10);",
707
+ " --dsh-mem-bg-card: var(--dsw-alias-bg-layer-2, #ffffff);",
708
+ " --dsh-mem-bg-inset: var(--dsw-alias-bg-overlay, #e9ecf2);",
709
+ " --dsh-mem-bg-hover: var(--dsw-alias-interactive-bg-hover, rgba(38,49,72,0.06));",
710
+ " --dsh-mem-bg-pop: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3, #ffffff));",
711
+ " --dsh-mem-border: var(--dsw-alias-border-l2, rgba(0,0,0,0.10));",
712
+ " --dsh-mem-border-strong: var(--dsw-alias-border-l3, rgba(0,0,0,0.12));",
713
+ " --dsh-mem-border-pop: var(--dsw-alias-border-inverted, rgba(0,0,0,0));",
714
+ " --dsh-mem-text-1: var(--dsw-alias-label-primary, #0f1115);",
715
+ " --dsh-mem-text-2: var(--dsw-alias-label-secondary, #61666b);",
716
+ " --dsh-mem-text-3: var(--dsw-alias-label-tertiary, #6e7781);",
717
+ " --dsh-mem-danger: var(--dsw-alias-state-error-primary, #d0403f);",
718
+ // 档位色 = 灰 → 品牌蓝 的渐变阶(chat/work 过渡蓝 / auto 品牌蓝);
719
+ // 文字对比度按 pill 真实底色(流光内底 = bg-card 97% + 档位色 3%)复算 AA 达标
720
+ " --dsh-mem-mode-chat: #5a69b0;",
721
+ " --dsh-mem-mode-work: #5263ca;",
722
+ " --dsh-mem-mode-auto: #3d5be0;",
723
+ // 滑轨填充渐变(左浅右深)
724
+ " --dsh-mem-fill-1: #7b93ff;",
725
+ " --dsh-mem-fill-2: #3d5be0;",
726
+ " --dsh-mem-thumb: #ffffff;",
727
+ " --dsh-mem-track: rgba(128,140,150,0.32);",
728
+ " --dsh-mem-dot: rgba(128,140,150,0.55);",
729
+ " --dsh-mem-shadow-card: var(--dsw-shadow-lv1, 0 2px 4px 0 rgba(0,0,0,0.05));",
730
+ " --dsh-mem-shadow-pop: var(--dsw-shadow-lv3, 0 0 1px 0 rgba(0,0,0,.2), 0 0 4px 0 rgba(0,0,0,.02), 0 12px 32px 0 rgba(0,0,0,.08));",
731
+ "}",
732
+ // ── 令牌(暗色:body[data-ds-dark-theme] 是 dsh 前端的暗色开关) ──
733
+ "body[data-ds-dark-theme] {",
734
+ " --dsh-mem-accent: #6e85ff;",
735
+ " --dsh-mem-accent-text: #7b90ff;",
736
+ " --dsh-mem-accent-fill: #465ce8;",
737
+ " --dsh-mem-accent-weak: rgba(110,133,255,0.14);",
738
+ " --dsh-mem-bg-card: var(--dsw-alias-bg-layer-2, #2c2c2e);",
739
+ " --dsh-mem-bg-inset: var(--dsw-alias-bg-layer-1, #232324);",
740
+ " --dsh-mem-bg-hover: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,0.08));",
741
+ " --dsh-mem-bg-pop: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3, #353638));",
742
+ " --dsh-mem-border: var(--dsw-alias-border-l2, rgba(255,255,255,0.12));",
743
+ " --dsh-mem-border-strong: var(--dsw-alias-border-l3, rgba(255,255,255,0.16));",
744
+ " --dsh-mem-border-pop: var(--dsw-alias-border-inverted, rgba(255,255,255,0.06));",
745
+ " --dsh-mem-text-1: var(--dsw-alias-label-primary, #f9fafb);",
746
+ " --dsh-mem-text-2: var(--dsw-alias-label-secondary, #cfd3d6);",
747
+ " --dsh-mem-text-3: var(--dsw-alias-label-tertiary, #8892a6);",
748
+ " --dsh-mem-danger: var(--dsw-alias-state-error-primary, #f4707b);",
749
+ " --dsh-mem-mode-chat: #97a4ff;",
750
+ " --dsh-mem-mode-work: #8295ff;",
751
+ " --dsh-mem-mode-auto: #7b90ff;",
752
+ " --dsh-mem-fill-1: #8fa0ff;",
753
+ " --dsh-mem-fill-2: #465ce8;",
754
+ " --dsh-mem-thumb: #e8ebf5;",
755
+ " --dsh-mem-track: rgba(148,160,180,0.30);",
756
+ " --dsh-mem-dot: rgba(148,160,180,0.5);",
757
+ " --dsh-mem-shadow-card: var(--dsw-shadow-lv1, 0 2px 4px 0 rgba(0,0,0,0.3));",
758
+ " --dsh-mem-shadow-pop: var(--dsw-shadow-lv3, 0 0 1px 0 rgba(0,0,0,.2), 0 0 4px 0 rgba(0,0,0,.02), 0 12px 32px 0 rgba(0,0,0,.08));",
759
+ "}",
760
+ // ── 主题切换过渡:只挂颜色/阴影(不碰 transform),让明暗翻转不生硬 ──
761
+ ".dsh-mem-root, .dsh-mem-root * { transition: background-color .18s ease, border-color .18s ease, color .18s ease, box-shadow .18s ease; }",
762
+ // ── 控件:按钮(次级)── 圆角体系:控件 8 / 卡片 10 / 浮层 12 / 胶囊 999
763
+ ".dsh-mem-btn {",
764
+ " padding: 5px 14px; font-size: 13px; line-height: 20px; border-radius: 8px; cursor: pointer;",
765
+ " border: 1px solid var(--dsh-mem-border); background: var(--dsh-mem-bg-card); color: var(--dsh-mem-text-1);",
766
+ " transition: background-color .15s ease, border-color .15s ease, transform .08s ease;",
767
+ "}",
768
+ ".dsh-mem-btn:hover:not(:disabled) { border-color: var(--dsh-mem-border-strong); background: var(--dsh-mem-bg-hover); }",
769
+ ".dsh-mem-btn:active:not(:disabled) { transform: scale(0.98); }",
770
+ ".dsh-mem-btn:focus-visible { outline: 2px solid var(--dsh-mem-accent); outline-offset: 1px; }",
771
+ ".dsh-mem-btn:disabled { opacity: 0.45; cursor: not-allowed; }",
772
+ // ── 控件:输入框 / 下拉 ──
773
+ ".dsh-mem-input, .dsh-mem-select {",
774
+ " padding: 5px 10px; font-size: 13px; border-radius: 8px; color: var(--dsh-mem-text-1);",
775
+ " border: 1px solid var(--dsh-mem-border); background: var(--dsh-mem-bg-card);",
776
+ " transition: border-color .15s ease, box-shadow .15s ease;",
777
+ "}",
778
+ ".dsh-mem-input:focus, .dsh-mem-select:focus {",
779
+ " outline: none; border-color: var(--dsh-mem-accent);",
780
+ " box-shadow: 0 0 0 3px var(--dsh-mem-accent-weak);",
781
+ "}",
782
+ // ── Tab(下划线式):active 品牌蓝下划线 + 主文字色 ──
783
+ ".dsh-mem-tab {",
784
+ " padding: 6px 12px; font-size: 13px; cursor: pointer; background: none; border: none;",
785
+ " color: var(--dsh-mem-text-2); border-bottom: 2px solid transparent; margin-bottom: -1px;",
786
+ "}",
787
+ ".dsh-mem-tab:hover { color: var(--dsh-mem-text-1); }",
788
+ ".dsh-mem-tab-on { font-weight: 600; color: var(--dsh-mem-text-1); border-bottom-color: var(--dsh-mem-accent); }",
789
+ ".dsh-mem-tab:focus-visible { outline: 2px solid var(--dsh-mem-accent); outline-offset: -2px; }",
790
+ // ── 卡片:悬浮微抬 + 边框加深(只在可交互卡片上用 hover) ──
791
+ ".dsh-mem-card {",
792
+ " border: 1px solid var(--dsh-mem-border); border-radius: 10px; background: var(--dsh-mem-bg-card);",
793
+ " box-shadow: var(--dsh-mem-shadow-card);",
794
+ "}",
795
+ ".dsh-mem-card-hover:hover { border-color: var(--dsh-mem-border-strong); }",
796
+ // ── 记忆类型标签:tint 风格(彩底淡色 + 彩字),--dsh-mem-tag-c 由类型类给定 ──
797
+ ".dsh-mem-tag {",
798
+ " display: inline-block; padding: 1px 8px; border-radius: 999px;",
799
+ " font-size: 11px; font-weight: 600; line-height: 18px; white-space: nowrap;",
800
+ " color: var(--dsh-mem-tag-c, var(--dsh-mem-text-2));",
801
+ " background: color-mix(in srgb, var(--dsh-mem-tag-c, #8a93a1) 12%, transparent);",
802
+ " border: 1px solid color-mix(in srgb, var(--dsh-mem-tag-c, #8a93a1) 28%, transparent);",
803
+ "}",
804
+ ".dsh-mem-tag-persona { --dsh-mem-tag-c: #6f42c1; }",
805
+ ".dsh-mem-tag-episodic { --dsh-mem-tag-c: #0757b4; }",
806
+ ".dsh-mem-tag-instruction, .dsh-mem-tag-work-artifact { --dsh-mem-tag-c: #8a5a00; }",
807
+ ".dsh-mem-tag-work-fact { --dsh-mem-tag-c: #0757b4; }",
808
+ ".dsh-mem-tag-work-task { --dsh-mem-tag-c: #116629; }",
809
+ ".dsh-mem-tag-work-method { --dsh-mem-tag-c: #6f42c1; }",
810
+ "body[data-ds-dark-theme] .dsh-mem-tag-persona { --dsh-mem-tag-c: #c297ff; }",
811
+ "body[data-ds-dark-theme] .dsh-mem-tag-episodic { --dsh-mem-tag-c: #6cb2ff; }",
812
+ "body[data-ds-dark-theme] .dsh-mem-tag-instruction, body[data-ds-dark-theme] .dsh-mem-tag-work-artifact { --dsh-mem-tag-c: #e3b341; }",
813
+ "body[data-ds-dark-theme] .dsh-mem-tag-work-fact { --dsh-mem-tag-c: #6cb2ff; }",
814
+ "body[data-ds-dark-theme] .dsh-mem-tag-work-task { --dsh-mem-tag-c: #6fca74; }",
815
+ "body[data-ds-dark-theme] .dsh-mem-tag-work-method { --dsh-mem-tag-c: #c297ff; }",
816
+ // ── pill 边缘流光(品牌蓝族,chat/work/auto 三档共用;off 无):border 区画旋转
817
+ // conic 光带;内部必须是【不透明】底色盖住光带(半透明内层会让 conic 透进按钮
818
+ // 内部,文字被光斑干扰——实测事故)。不透明底 = 主题底混 3% 档位色
819
+ // (--dsh-mem-pill-tint 由 pill inline 给定;3% 保证档位色文字 AA,暗色智能档余量 4.63:1)
509
820
  ".dsh-mem-flow {",
510
821
  " border: 1px solid transparent;",
511
822
  " background:",
512
823
  " linear-gradient(",
513
- " color-mix(in srgb, var(--dsw-alias-bg-layer-2, #ffffff) 88%, #3b82f6),",
514
- " color-mix(in srgb, var(--dsw-alias-bg-layer-2, #ffffff) 88%, #3b82f6)",
824
+ " color-mix(in srgb, var(--dsh-mem-bg-card, #ffffff) 97%, var(--dsh-mem-pill-tint, #4d6bfe)),",
825
+ " color-mix(in srgb, var(--dsh-mem-bg-card, #ffffff) 97%, var(--dsh-mem-pill-tint, #4d6bfe))",
515
826
  " ) padding-box,",
516
827
  " conic-gradient(from var(--dsh-mem-angle),",
517
- " rgba(8,145,178,0.9), rgba(59,130,246,0.9), rgba(125,211,252,1),",
518
- " rgba(99,102,241,0.9), rgba(8,145,178,0.9)) border-box;",
828
+ " rgba(61,91,224,0.9), rgba(77,107,254,0.95), rgba(147,168,255,1),",
829
+ " rgba(110,133,255,0.9), rgba(61,91,224,0.9)) border-box;",
519
830
  " animation: dshMemFlow 3s linear infinite;",
520
- " color: #0284c7;",
521
831
  "}",
522
- "body[data-ds-dark-theme] .dsh-mem-flow { color: #7dd3fc; }",
523
- "@media (prefers-reduced-motion: reduce) { .dsh-mem-flow { animation: none; } }",
524
- // 浮层玻璃材质(Apple 菜单配方):材质层必须是【无 transform 的独立元素】——
525
- // Chromium transform 元素上的 backdrop-filter 采样异常(玻璃失效,实测)。
526
- // 低不透明度基底让底下内容透出 + 顶部光泽渐变 + blur/saturate + 亮缘 + 分主题阴影
527
- ".dsh-mem-glass {",
832
+ // ── off 档 pill:dsh 透明按钮——无底无边框只留文字,hover 才出 interactive 淡底 ──
833
+ //(button 裸元素会露出 UA 默认灰底+描边,必须显式压掉)
834
+ ".dsh-mem-pill-off { border: none; background: transparent; }",
835
+ ".dsh-mem-pill-off:hover { background: var(--dsh-mem-bg-hover); }",
836
+ ".dsh-mem-pill-off:focus-visible { outline: 2px solid var(--dsh-mem-accent); outline-offset: 1px; }",
837
+ // 流光态焦点环(同一物理按钮的两态焦点反馈对称,配方同 .dsh-mem-btn)
838
+ ".dsh-mem-flow:focus-visible { outline: 2px solid var(--dsh-mem-accent); outline-offset: 1px; }",
839
+ // ── 浮层(dsh 原生菜单同配方:不透明实底 + inverted 描边(浅色不可见)+ lv3 阴影) ──
840
+ ".dsh-mem-popover {",
528
841
  " border-radius: 12px;",
529
- " background:",
530
- " linear-gradient(rgba(255,255,255,0.34), rgba(255,255,255,0.05)),",
531
- " rgba(248,249,251,0.55);",
532
- " -webkit-backdrop-filter: blur(32px) saturate(180%);",
533
- " backdrop-filter: blur(32px) saturate(180%);",
534
- " border: 1px solid rgba(255,255,255,0.55);",
535
- " box-shadow:",
536
- " 0 12px 40px rgba(0,0,0,0.16), 0 2px 8px rgba(0,0,0,0.07),",
537
- " inset 0 1px 0 rgba(255,255,255,0.55), inset 0 -1px 0 rgba(0,0,0,0.03);",
842
+ " border: 1px solid var(--dsh-mem-border-pop);",
843
+ " background: var(--dsh-mem-bg-pop);",
844
+ " box-shadow: var(--dsh-mem-shadow-pop);",
845
+ " color: var(--dsh-mem-text-1);",
538
846
  "}",
539
- "body[data-ds-dark-theme] .dsh-mem-glass {",
540
- " background:",
541
- " linear-gradient(rgba(255,255,255,0.09), rgba(255,255,255,0.015)),",
542
- " rgba(30,32,40,0.55);",
543
- " border-color: rgba(255,255,255,0.14);",
544
- " box-shadow:",
545
- " 0 16px 48px rgba(0,0,0,0.55), 0 3px 12px rgba(0,0,0,0.35),",
546
- " inset 0 1px 0 rgba(255,255,255,0.12);",
847
+ // ── 拖动气泡:拖拽时显示当前档位名,随圆球移动,下倒三角尖角贴近圆球 ──
848
+ // 底色走浮层同材质令牌(浅色白底深字 / 暗色深底浅字,随主题翻转;
849
+ // tooltip-bg 令牌在浅色下仍是深色,不随材质走,已弃用)。
850
+ // 悬停 8px(尖角尖端距圆球顶约 5px);气泡 zIndex 4 高于浮层(同层叠上下文内
851
+ // 数值比较),跨过浮层上缘时盖在其上;描边 + 投影让同材质不融合
852
+ ".dsh-mem-bubble {",
853
+ " position: absolute; bottom: calc(100% + 8px); transform: translateX(-50%);",
854
+ " padding: 3px 10px; border-radius: 8px; font-size: 12px; font-weight: 600; line-height: 18px;",
855
+ " border: 1px solid var(--dsh-mem-border);",
856
+ " background: var(--dsh-mem-bg-pop); color: var(--dsh-mem-text-1); white-space: nowrap;",
857
+ " box-shadow: 0 2px 8px rgba(0,0,0,0.18);",
858
+ "}",
859
+ // 尖角:clip-path 倒三角(旋转方块会露出上半截成菱形,实测视觉缺陷)。
860
+ // 双三角叠画:外层描边色大一圈、内层填充色,压在浮层上缘也有轮廓可读
861
+ ".dsh-mem-bubble::before {",
862
+ " content: ''; position: absolute; top: 100%; left: 50%; margin-left: -6px;",
863
+ " width: 12px; height: 7px;",
864
+ " clip-path: polygon(0 0, 100% 0, 50% 100%);",
865
+ " background: var(--dsh-mem-border);",
866
+ "}",
867
+ ".dsh-mem-bubble::after {",
868
+ " content: ''; position: absolute; top: 100%; left: 50%; margin-left: -5px;",
869
+ " width: 10px; height: 6px;",
870
+ " clip-path: polygon(0 0, 100% 0, 50% 100%);",
871
+ " background: var(--dsh-mem-bg-pop);",
547
872
  "}",
548
- "@media (prefers-reduced-transparency: reduce) {",
549
- " .dsh-mem-glass { background: var(--dsw-alias-bg-layer-2, #ffffff); backdrop-filter: none; -webkit-backdrop-filter: none; }",
873
+ // ── 粒子层(点阵场):浅色 multiply 混合——深蓝点乘在浅蓝填充上沉显对比 ──
874
+ "body:not([data-ds-dark-theme]) .dsh-mem-particles { mix-blend-mode: multiply; opacity: 0.82; }",
875
+ // ── 重建面板 ──(模态本体走 NModal:原生 Modal 优先,回退 rb-overlay/rb-modal)
876
+ ".dsh-mem-rb-card {",
877
+ " border: 1px solid var(--dsh-mem-border); border-radius: 10px; background: var(--dsh-mem-bg-card);",
878
+ " box-shadow: var(--dsh-mem-shadow-card); padding: 12px 14px; margin-bottom: 14px; font-size: 13px;",
879
+ "}",
880
+ ".dsh-mem-rb-bar { height: 8px; border-radius: 4px; overflow: hidden; flex: 1; background: var(--dsh-mem-track); }",
881
+ ".dsh-mem-rb-fill { height: 100%; border-radius: 4px; background: var(--dsh-mem-accent-fill); transition: width .4s ease; }",
882
+ ".dsh-mem-rb-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.35);",
883
+ " display: flex; align-items: center; justify-content: center; z-index: 2000; }",
884
+ ".dsh-mem-rb-modal { width: 440px; max-width: calc(100vw - 48px); border-radius: 12px;",
885
+ " padding: 18px 20px; font-size: 13px; line-height: 1.6;",
886
+ " border: 1px solid var(--dsh-mem-border); background: var(--dsh-mem-bg-card); color: var(--dsh-mem-text-1);",
887
+ " box-shadow: 0 16px 48px rgba(0,0,0,0.24); }",
888
+ "body[data-ds-dark-theme] .dsh-mem-rb-modal { box-shadow: 0 16px 48px rgba(0,0,0,0.6); }",
889
+ ".dsh-mem-rb-muted { font-size: 12px; color: var(--dsh-mem-text-3); }",
890
+ // reduced-motion 兜底放样式表末尾:同特异性下后置声明才能压过上面的组件类
891
+ "@media (prefers-reduced-motion: reduce) {",
892
+ " .dsh-mem-root, .dsh-mem-root *, .dsh-mem-btn, .dsh-mem-input, .dsh-mem-select, .dsh-mem-rb-fill { transition: none; }",
893
+ " .dsh-mem-flow { animation: none; }",
550
894
  "}",
551
895
  ].join("\n");
552
896
  document.head.appendChild(el);
@@ -566,16 +910,31 @@ window.__ModuleLoader__.load({
566
910
  var open = openState[0];
567
911
  var setOpen = openState[1];
568
912
  var wrapRef = react.useRef(null);
913
+ // 请求序列号:快速切换会话时丢弃旧会话的过期响应(慢响应不得覆盖新会话档位)
914
+ var seqRef = react.useRef(0);
569
915
 
570
- react.useEffect(function () {
916
+ var load = react.useCallback(function () {
571
917
  if (!sessionId || !rpc) return;
918
+ var token = ++seqRef.current;
919
+ setError(null);
572
920
  rpc("dsh-memory/session-mode-get", { sessionId: sessionId })
573
921
  .then(function (r) {
922
+ if (token !== seqRef.current) return;
574
923
  if (r && r.ok && r.value) setMode(r.value.mode);
924
+ else setError(r && r.error ? r.error.message : "RPC error");
575
925
  })
576
- .catch(function () {});
926
+ .catch(function (e) {
927
+ if (token !== seqRef.current) return;
928
+ setError(String((e && e.message) || e));
929
+ });
577
930
  }, [sessionId, rpc]);
578
931
 
932
+ react.useEffect(function () { load(); }, [load]);
933
+
934
+ // 侧边栏书本 icon 补丁由常驻 pill 驱动(MemoryPanel 只在记忆分节激活时挂载,
935
+ // 覆盖不了"打开设置第一眼"的场景);body 级观察器全局单例,多实例幂等
936
+ react.useEffect(function () { watchSidebarIcon(); }, []);
937
+
579
938
  react.useEffect(function () {
580
939
  if (!open) return;
581
940
  var onDown = function (e) {
@@ -614,12 +973,13 @@ window.__ModuleLoader__.load({
614
973
  if (!sessionId || !rpc) return null;
615
974
  var info = modeInfo(mode);
616
975
  var loaded = mode !== null;
617
- var isAuto = loaded && mode === "auto";
976
+ // 关闭档与其余三档二分:关闭 = dsh 透明按钮(无边框无底无光晕);
977
+ // 日常/工作/智能 = 同款流光 + 光晕,档位区分靠蓝阶文字色与流光内底混色深度
978
+ var isOff = loaded && mode === "off";
979
+ var isFlow = loaded && !isOff;
618
980
 
619
- if (isAuto) ensureFlowStyle();
981
+ ensureThemeStyle();
620
982
 
621
- // auto 档的边框/背景/文字色均由 .dsh-mem-flow 提供(双层背景画流光边 + 分主题文字),
622
- // inline 不能设置这些——inline 优先级会盖掉 class 里的流光
623
983
  var pillStyle = {
624
984
  display: "inline-flex",
625
985
  alignItems: "center",
@@ -631,13 +991,13 @@ window.__ModuleLoader__.load({
631
991
  fontWeight: 500,
632
992
  lineHeight: "20px",
633
993
  cursor: "pointer",
994
+ // 流光档的边框/背景由 .dsh-mem-flow 的双层背景提供(流光边 + 不透明内底),
995
+ // inline 只给文字色 / 光晕 / 流光内底混色通道(--dsh-mem-pill-tint)
996
+ color: isFlow ? info.color : "var(--dsh-mem-text-2)",
634
997
  };
635
- if (isAuto) {
636
- pillStyle.boxShadow = "0 0 12px rgba(56,189,248,0.30)";
637
- } else {
638
- pillStyle.border = "1px solid " + (loaded ? info.color + "66" : "var(--dsw-alias-border-secondary, #ccc)");
639
- pillStyle.background = loaded ? info.color + "14" : "var(--dsw-alias-bg-secondary, #f6f8fa)";
640
- pillStyle.color = loaded ? info.color : "var(--dsh-alias-label-tertiary, #888)";
998
+ if (isFlow) {
999
+ pillStyle.boxShadow = "0 0 12px color-mix(in srgb, " + info.color + " 30%, transparent)";
1000
+ pillStyle["--dsh-mem-pill-tint"] = info.color;
641
1001
  }
642
1002
 
643
1003
  return react.createElement(
@@ -647,13 +1007,16 @@ window.__ModuleLoader__.load({
647
1007
  "button",
648
1008
  {
649
1009
  type: "button",
650
- title: "本会话记忆档位(点击切换)",
651
- onClick: function () { setOpen(!open); },
652
- className: isAuto ? "dsh-mem-flow" : null,
1010
+ title: error ? "档位读取失败:" + error + "(点击重试)" : "本会话记忆档位(点击切换)",
1011
+ onClick: function () {
1012
+ if (error) load();
1013
+ setOpen(!open);
1014
+ },
1015
+ className: isFlow ? "dsh-mem-flow" : "dsh-mem-pill-off",
653
1016
  style: pillStyle,
654
1017
  },
655
- "记忆·",
656
- react.createElement("span", null, loaded ? info.label : "…"),
1018
+ "记忆 · ",
1019
+ react.createElement("span", null, loaded ? info.label : error ? "⚠" : "…"),
657
1020
  ),
658
1021
  open
659
1022
  ? react.createElement(ModeSlider, {
@@ -665,6 +1028,441 @@ window.__ModuleLoader__.load({
665
1028
  );
666
1029
  }
667
1030
 
1031
+ // ── 重建面板:从 L0 重导 L1/L2/L3(确认弹窗 + 进度 + 取消;Light/Dark 双主题走 class) ──
1032
+ var RB_PHASE_LABEL = {
1033
+ preparing: "准备中(归档旧数据 · 清空检索库)",
1034
+ distilling: "分块蒸馏中",
1035
+ finalizing: "收尾(强制 L2 场景 + L3 画像)",
1036
+ };
1037
+
1038
+ function RebuildPanel(props) {
1039
+ var rpc = props.rpc;
1040
+ var rbState = react.useState(null);
1041
+ var rb = rbState[0];
1042
+ var setRb = rbState[1];
1043
+ var confirmState = react.useState(false);
1044
+ var confirmOpen = confirmState[0];
1045
+ var setConfirmOpen = confirmState[1];
1046
+ var busyState = react.useState(false);
1047
+ var busy = busyState[0];
1048
+ var setBusy = busyState[1];
1049
+ var errState = react.useState(null);
1050
+ var rbError = errState[0];
1051
+ var setRbError = errState[1];
1052
+
1053
+ var refresh = react.useCallback(function () {
1054
+ rpc("dsh-memory/rebuild-status", {})
1055
+ .then(function (r) {
1056
+ if (r && r.ok) setRb(r.value);
1057
+ })
1058
+ .catch(function () {});
1059
+ }, [rpc]);
1060
+
1061
+ react.useEffect(function () { refresh(); }, [refresh]);
1062
+
1063
+ // 运行中 1.5s 高频轮询进度;空闲不轮询(重新打开 Tab 时挂载刷新一次)
1064
+ var running = !!(rb && rb.running);
1065
+ react.useEffect(function () {
1066
+ if (!running) return;
1067
+ var timer = setInterval(refresh, 1500);
1068
+ return function () { clearInterval(timer); };
1069
+ }, [running, refresh]);
1070
+
1071
+ if (!rb || rb.supported === false) return null;
1072
+ ensureThemeStyle();
1073
+
1074
+ var start = function () {
1075
+ setBusy(true);
1076
+ setRbError(null);
1077
+ rpc("dsh-memory/rebuild-start", {})
1078
+ .then(function (r) {
1079
+ setBusy(false);
1080
+ if (r && r.ok) {
1081
+ setConfirmOpen(false);
1082
+ setRb(r.value);
1083
+ } else {
1084
+ setRbError(r && r.error ? r.error.message : "启动失败");
1085
+ }
1086
+ })
1087
+ .catch(function (e) {
1088
+ setBusy(false);
1089
+ setRbError(String((e && e.message) || e));
1090
+ });
1091
+ };
1092
+ var cancel = function () {
1093
+ setBusy(true);
1094
+ rpc("dsh-memory/rebuild-cancel", {})
1095
+ .then(function (r) {
1096
+ setBusy(false);
1097
+ if (r && r.ok) setRb(r.value);
1098
+ })
1099
+ .catch(function () {
1100
+ setBusy(false);
1101
+ });
1102
+ };
1103
+
1104
+ var empty = !running && (rb.messageCount === 0 || rb.estCalls === 0);
1105
+ var pct = rb.total > 0 ? Math.round((rb.done / rb.total) * 100) : 0;
1106
+
1107
+ var lastNote = null;
1108
+ if (!running && rb.phase === "done") {
1109
+ lastNote =
1110
+ "上次重建:完成(" + rb.done + "/" + rb.total + " 会话,产出 " + rb.recordsBuilt + " 条记录)" +
1111
+ (rb.finishedAt ? " · " + fmtTime(new Date(rb.finishedAt).toISOString()) : "");
1112
+ } else if (!running && rb.phase === "cancelled") {
1113
+ lastNote = "上次重建:已取消(完成 " + rb.done + "/" + rb.total + " 会话,已重建部分保留)";
1114
+ } else if (!running && rb.phase === "failed") {
1115
+ lastNote = "上次重建:失败:" + (rb.error || "未知错误");
1116
+ }
1117
+
1118
+ return react.createElement(
1119
+ "div", { className: "dsh-mem-rb-card" },
1120
+ react.createElement(
1121
+ "div", { style: { display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" } },
1122
+ react.createElement("div", { style: { fontWeight: 600, whiteSpace: "nowrap" } }, "重建记忆"),
1123
+ react.createElement(
1124
+ "div", { className: "dsh-mem-rb-muted", style: { flex: 1, minWidth: 180 } },
1125
+ running
1126
+ ? (RB_PHASE_LABEL[rb.phase] || rb.phase) + " · " + rb.done + "/" + rb.total + " 会话(" + pct + "%)"
1127
+ : "从 L0 原始对话重新蒸馏 L1/L2/L3;旧数据先归档(不删除)",
1128
+ ),
1129
+ running
1130
+ ? react.createElement(NButton, {
1131
+ disabled: busy || rb.cancelRequested,
1132
+ onClick: cancel,
1133
+ }, rb.cancelRequested ? "取消中…" : "取消重建")
1134
+ : react.createElement(NButton, {
1135
+ disabled: busy || empty,
1136
+ title: empty ? "L0 无消息,无可重建内容" : "重新蒸馏全部记忆",
1137
+ style: { color: "var(--dsh-mem-danger)" },
1138
+ onClick: function () { setConfirmOpen(true); },
1139
+ }, busy ? "…" : "开始重建"),
1140
+ ),
1141
+ running
1142
+ ? react.createElement(
1143
+ "div", { style: { display: "flex", alignItems: "center", gap: 10, marginTop: 10 } },
1144
+ react.createElement(
1145
+ "div", { className: "dsh-mem-rb-bar" },
1146
+ react.createElement("div", { className: "dsh-mem-rb-fill", style: { width: pct + "%" } }),
1147
+ ),
1148
+ react.createElement(
1149
+ "span", { className: "dsh-mem-rb-muted", style: { whiteSpace: "nowrap" } },
1150
+ "产出 " + rb.recordsBuilt + " 条",
1151
+ ),
1152
+ )
1153
+ : null,
1154
+ lastNote
1155
+ ? react.createElement("div", { className: "dsh-mem-rb-muted", style: { marginTop: 8 } }, lastNote)
1156
+ : null,
1157
+ rbError
1158
+ ? react.createElement("div", { style: { marginTop: 8, fontSize: 12, color: "var(--dsh-mem-danger)" } }, rbError)
1159
+ : null,
1160
+ confirmOpen
1161
+ ? react.createElement(
1162
+ NModal,
1163
+ {
1164
+ open: true,
1165
+ onClose: function () { setConfirmOpen(false); },
1166
+ title: "确认重建全部记忆?",
1167
+ footer: [
1168
+ react.createElement(NButton, {
1169
+ key: "cancel",
1170
+ onClick: function () { setConfirmOpen(false); },
1171
+ }, "取消"),
1172
+ react.createElement(NButton, {
1173
+ key: "confirm",
1174
+ variant: "primary",
1175
+ disabled: busy,
1176
+ onClick: start,
1177
+ }, busy ? "启动中…" : "开始重建"),
1178
+ ],
1179
+ },
1180
+ react.createElement(
1181
+ "div", null,
1182
+ "将以 L0 原始对话为事实源重新蒸馏:",
1183
+ react.createElement("b", null, rb.sessionCount + " 个会话 · " + rb.messageCount + " 条消息"),
1184
+ ",预计 ≥" + rb.estCalls + " 次蒸馏调用。",
1185
+ ),
1186
+ react.createElement(
1187
+ "div", { style: { marginTop: 8 } },
1188
+ "现有 L1 记忆 / L2 场景 / L3 画像会整体归档(*.bak.时间戳,可手工找回),随后清空重建;重建期间可正常对话,新对话的蒸馏优先进行;中途可取消,已重建部分保留。",
1189
+ ),
1190
+ )
1191
+ : null,
1192
+ );
1193
+ }
1194
+
1195
+ // ── Tab:概览(开关面板 + 计数) ──
1196
+ function fmtMB(bytes) {
1197
+ if (!bytes || bytes <= 0) return "0MB";
1198
+ if (bytes < 1024 * 1024) return Math.round(bytes / 1024) + "KB";
1199
+ return (bytes / (1024 * 1024)).toFixed(bytes < 100 * 1024 * 1024 ? 1 : 0) + "MB";
1200
+ }
1201
+
1202
+ /** 嵌入源区块(D4 三态单选 + D7 下载进度 + D5 重嵌进度;1.2s 轮询,不能让用户傻等)。 */
1203
+ function EmbeddingSection(props) {
1204
+ var rpc = props.rpc;
1205
+ var stState = react.useState(null);
1206
+ var st = stState[0];
1207
+ var setSt = stState[1];
1208
+ var errState = react.useState(null);
1209
+ var err = errState[0];
1210
+ var setErr = errState[1];
1211
+ // 轮询退避标志(load 维护):snapshot 每次几十次 fs.stat,空闲常开 1.2s 是白烧
1212
+ var busyPollRef = react.useRef(null);
1213
+
1214
+ var load = react.useCallback(function () {
1215
+ rpc("dsh-memory/embedding-state-get", {})
1216
+ .then(function (r) {
1217
+ if (r && r.ok && r.value && r.value.supported !== false) {
1218
+ var v = r.value;
1219
+ if (busyPollRef.current) {
1220
+ busyPollRef.current.v = !!(
1221
+ (v.download && (v.download.phase === "downloading" || v.download.phase === "verifying")) ||
1222
+ v.apply.busy ||
1223
+ (v.reindex && v.reindex.running) ||
1224
+ (v.runtime && v.runtime.phase === "installing")
1225
+ );
1226
+ }
1227
+ setSt(v);
1228
+ setErr(null);
1229
+ } else if (r && r.ok && r.value && r.value.supported === false) {
1230
+ setSt(null);
1231
+ setErr("__unsupported__");
1232
+ } else {
1233
+ setErr(r && r.error ? r.error.message : "RPC error");
1234
+ }
1235
+ })
1236
+ .catch(function (e) {
1237
+ setErr(String((e && e.message) || e));
1238
+ });
1239
+ }, [rpc]);
1240
+
1241
+ react.useEffect(function () { load(); }, [load]);
1242
+ react.useEffect(function () {
1243
+ var stopped = false;
1244
+ var busyFlag = { v: false };
1245
+ busyPollRef.current = busyFlag;
1246
+ var tick = function () {
1247
+ if (stopped) return;
1248
+ load();
1249
+ timer = setTimeout(tick, busyFlag.v ? 1200 : 5000);
1250
+ };
1251
+ var timer = setTimeout(tick, 1200);
1252
+ return function () {
1253
+ stopped = true;
1254
+ clearTimeout(timer);
1255
+ busyPollRef.current = null;
1256
+ };
1257
+ }, [load]);
1258
+
1259
+ var call = function (endpoint, payload, confirmText) {
1260
+ if (confirmText && !window.confirm(confirmText)) return;
1261
+ rpc(endpoint, payload)
1262
+ .then(function (r) {
1263
+ if (!r || !r.ok) setErr(r && r.error ? r.error.message : "操作失败");
1264
+ else { setErr(null); load(); }
1265
+ })
1266
+ .catch(function (e) { setErr(String((e && e.message) || e)); });
1267
+ };
1268
+
1269
+ if (err === "__unsupported__") {
1270
+ return react.createElement(
1271
+ "div", { className: "dsh-mem-rb-card" },
1272
+ react.createElement("div", { style: { fontWeight: 600, marginBottom: 4 } }, "语义检索(嵌入)"),
1273
+ react.createElement("div", { className: "dsh-mem-rb-muted" }, "存储处于降级状态,嵌入管理不可用。"),
1274
+ );
1275
+ }
1276
+ if (!st) {
1277
+ return react.createElement(
1278
+ "div", { className: "dsh-mem-rb-card" },
1279
+ react.createElement("div", { className: "dsh-mem-rb-muted" }, err ? "嵌入状态读取失败:" + err : "嵌入状态读取中…"),
1280
+ err ? react.createElement(NButton, { style: { marginTop: 8 }, onClick: load }, "重试") : null,
1281
+ );
1282
+ }
1283
+
1284
+ var switchConfirm = "切换嵌入源后将按新模型重建向量索引(期间语义检索暂退化为关键词匹配,不影响对话)。确定切换?";
1285
+ var onSource = function (key) {
1286
+ if (key === "local") {
1287
+ // 本地档需要具体模型:有已下载模型直接用最新的启用;否则提示先下载
1288
+ var ready = [];
1289
+ for (var i = 0; i < st.models.length; i++) if (st.models[i].state === "downloaded") ready.push(st.models[i].id);
1290
+ if (ready.length === 0) {
1291
+ setErr("请先在下方下载一个本地嵌入模型,再切换到本地档。");
1292
+ return;
1293
+ }
1294
+ // 默认取目录序首个已下载模型(目录按轻→重排序,避免默认选中最重最慢的)
1295
+ var current = st.activeModel && ready.indexOf(st.activeModel) >= 0 ? st.activeModel : ready[0];
1296
+ call("dsh-memory/embedding-source-set", { source: "local", activeModel: current }, switchConfirm);
1297
+ return;
1298
+ }
1299
+ call("dsh-memory/embedding-source-set", { source: key }, key === "remote" ? switchConfirm : null);
1300
+ };
1301
+
1302
+ var dl = st.download;
1303
+ var dlActive = dl && (dl.phase === "downloading" || dl.phase === "verifying");
1304
+ var ap = st.apply;
1305
+ var localInfo = st.local;
1306
+ var rt = st.runtime;
1307
+
1308
+ var runtimeRow = null;
1309
+ if (rt.phase === "installing") {
1310
+ runtimeRow = react.createElement(
1311
+ "div", { style: { marginTop: 8, fontSize: 12 } },
1312
+ react.createElement("div", { style: S.flexRow },
1313
+ react.createElement("span", null, "安装推理运行时中… 已耗时 " + Math.round(rt.elapsedMs / 1000) + "s(约 100~200MB,视网络)"),
1314
+ react.createElement("div", { style: S.grow }),
1315
+ react.createElement(NButton, { onClick: function () { call("dsh-memory/embedding-runtime-cancel", {}); } }, "取消"),
1316
+ ),
1317
+ react.createElement(
1318
+ "pre",
1319
+ { style: Object.assign({}, S.pre, { maxHeight: 68, marginTop: 6, fontSize: 11, opacity: 0.85 }) },
1320
+ (rt.lastLines || []).join("\n") || "等待 npm 输出…",
1321
+ ),
1322
+ );
1323
+ } else if (rt.phase === "error") {
1324
+ runtimeRow = react.createElement(
1325
+ "div", { style: { marginTop: 8, fontSize: 12, color: "var(--dsh-mem-danger)" } },
1326
+ "运行时安装失败:" + (rt.error || "未知") + "(重新切换嵌入源可重试)",
1327
+ );
1328
+ } else if (rt.phase === "ready") {
1329
+ runtimeRow = react.createElement(
1330
+ "div", { className: "dsh-mem-rb-muted", style: { marginTop: 8 } },
1331
+ "推理运行时就绪(transformers.js v" + rt.installedVersion + ")",
1332
+ );
1333
+ } else if (st.source === "local") {
1334
+ runtimeRow = react.createElement(
1335
+ "div", { className: "dsh-mem-rb-muted", style: { marginTop: 8 } },
1336
+ "首次启用本地嵌入时会自动安装推理运行时(约 100~200MB)。",
1337
+ );
1338
+ }
1339
+
1340
+ var applyRow = null;
1341
+ if (ap.phase === "warming") {
1342
+ applyRow = react.createElement("div", { className: "dsh-mem-rb-muted", style: { marginTop: 8 } }, "加载嵌入模型中…(首次需数秒)");
1343
+ } else if (ap.phase === "switching") {
1344
+ applyRow = react.createElement("div", { className: "dsh-mem-rb-muted", style: { marginTop: 8 } }, "切换嵌入源中…");
1345
+ } else if (ap.phase === "error") {
1346
+ applyRow = react.createElement("div", { style: { marginTop: 8, fontSize: 12, color: "var(--dsh-mem-danger)" } }, "切换失败:" + ap.message + "(已保存的嵌入源不变,重启后仍按原源运行)");
1347
+ } else if (st.reindex && st.reindex.running) {
1348
+ var rj = st.reindex;
1349
+ var rDone = rj.l1Done + rj.l0Done;
1350
+ var rTotal = rj.l1Total + rj.l0Total;
1351
+ var rPct = rTotal > 0 ? Math.round((rDone / rTotal) * 100) : 0;
1352
+ applyRow = react.createElement(
1353
+ "div", { style: { marginTop: 8 } },
1354
+ react.createElement("div", { style: S.flexRow },
1355
+ react.createElement("span", { className: "dsh-mem-rb-muted" },
1356
+ "重嵌入中 L1 " + rj.l1Done + "/" + rj.l1Total + " · L0 " + rj.l0Done + "/" + rj.l0Total + "(" + rPct + "%)"),
1357
+ react.createElement("div", { style: S.grow }),
1358
+ react.createElement(NButton, { onClick: function () { call("dsh-memory/embedding-reindex-cancel", {}); } }, "取消"),
1359
+ ),
1360
+ react.createElement(
1361
+ "div", { style: Object.assign({}, S.flexRow, { marginTop: 6 }) },
1362
+ react.createElement("div", { className: "dsh-mem-rb-bar" }, react.createElement("div", { className: "dsh-mem-rb-fill", style: { width: rPct + "%" } })),
1363
+ ),
1364
+ );
1365
+ }
1366
+
1367
+ var modelCards = st.models.map(function (m) {
1368
+ var isActive = st.source === "local" && st.activeModel === m.id;
1369
+ var mDl = dlActive && dl.modelId === m.id;
1370
+ var pct = mDl && dl.overallTotal > 0 ? Math.round((dl.overallReceived / dl.overallTotal) * 100) : 0;
1371
+ var action = null;
1372
+ if (mDl) {
1373
+ action = react.createElement(
1374
+ "div", { style: { flex: 1, minWidth: 200 } },
1375
+ react.createElement("div", { style: S.flexRow },
1376
+ react.createElement("span", { className: "dsh-mem-rb-muted", style: { whiteSpace: "nowrap" } },
1377
+ (dl.phase === "verifying" ? "校验中 " : "") +
1378
+ fmtMB(dl.overallReceived) + " / " + fmtMB(dl.overallTotal) + "(文件 " + dl.fileIndex + "/" + dl.fileCount + "," + pct + "%" +
1379
+ (dl.speedBps > 0 && dl.phase === "downloading" ? "," + fmtMB(dl.speedBps) + "/s" : "") + ")"),
1380
+ react.createElement("div", { style: S.grow }),
1381
+ react.createElement(NButton, { onClick: function () { call("dsh-memory/embedding-download-cancel", {}); } }, "取消"),
1382
+ ),
1383
+ react.createElement(
1384
+ "div", { style: Object.assign({}, S.flexRow, { marginTop: 6 }) },
1385
+ react.createElement("div", { className: "dsh-mem-rb-bar" }, react.createElement("div", { className: "dsh-mem-rb-fill", style: { width: pct + "%" } })),
1386
+ ),
1387
+ );
1388
+ } else if (isActive) {
1389
+ action = react.createElement(
1390
+ "div", { style: S.flexRow },
1391
+ react.createElement("span", { className: "dsh-mem-tag dsh-mem-tag-work-task" }, "使用中"),
1392
+ localInfo && localInfo.state === "loading" ? react.createElement("span", { className: "dsh-mem-rb-muted" }, "模型加载中…") : null,
1393
+ localInfo && localInfo.state === "failed"
1394
+ ? react.createElement("span", { style: { fontSize: 12, color: "var(--dsh-mem-danger)" } }, "加载失败:" + (localInfo.error || ""))
1395
+ : null,
1396
+ localInfo && localInfo.state === "ready" ? react.createElement("span", { className: "dsh-mem-rb-muted" }, "已就绪") : null,
1397
+ );
1398
+ } else if (m.state === "downloaded") {
1399
+ action = react.createElement(
1400
+ "div", { style: S.flexRow },
1401
+ react.createElement(NButton, { disabled: ap.busy, onClick: function () {
1402
+ call("dsh-memory/embedding-source-set", { source: "local", activeModel: m.id }, switchConfirm);
1403
+ } }, "启用"),
1404
+ react.createElement(NButton, { disabled: dlActive, onClick: function () {
1405
+ call("dsh-memory/embedding-model-delete", { modelId: m.id }, "删除已下载的 " + m.name + "(" + fmtMB(m.totalBytes) + ")?");
1406
+ } }, "删除"),
1407
+ );
1408
+ } else {
1409
+ action = react.createElement(NButton, {
1410
+ disabled: dlActive || !st.ceilings.local,
1411
+ title: !st.ceilings.local ? "部署已禁用本地嵌入模型" : "",
1412
+ onClick: function () { call("dsh-memory/embedding-download-start", { modelId: m.id }); },
1413
+ }, (m.state === "partial" ? "继续下载 " : "下载 ") + fmtMB(m.totalBytes));
1414
+ }
1415
+ return react.createElement(
1416
+ "div",
1417
+ { key: m.id, style: Object.assign({}, S.flexRow, { padding: "8px 0", borderBottom: "1px solid var(--dsh-mem-border)", flexWrap: "wrap" }) },
1418
+ react.createElement(
1419
+ "div", { style: { minWidth: 150 } },
1420
+ react.createElement("div", { style: { fontWeight: 600 } }, m.name),
1421
+ react.createElement("div", { className: "dsh-mem-rb-muted" }, m.tags.join(" · ") + " · " + m.dims + " 维 · 上下文 " + m.contextTokens),
1422
+ ),
1423
+ react.createElement("div", { style: { flex: 1, minWidth: 180, fontSize: 12, color: "var(--dsh-mem-text-2)" } }, m.description),
1424
+ action,
1425
+ );
1426
+ });
1427
+
1428
+ return react.createElement(
1429
+ "div", { className: "dsh-mem-rb-card" },
1430
+ react.createElement(
1431
+ "div", { style: S.flexRow },
1432
+ react.createElement("div", { style: { fontWeight: 600, whiteSpace: "nowrap" } }, "语义检索(嵌入源)"),
1433
+ react.createElement("div", { style: S.grow }),
1434
+ react.createElement(Segmented, {
1435
+ value: st.source,
1436
+ options: [
1437
+ { key: "off", label: "关闭" },
1438
+ { key: "local", label: "本地", disabled: !st.ceilings.local, disabledTitle: "部署已禁用本地嵌入模型" },
1439
+ { key: "remote", label: "远程", disabled: !st.ceilings.remote, disabledTitle: "部署未配置远程嵌入(baseUrl/apiKey/model/dimensions)" },
1440
+ ],
1441
+ onChange: onSource,
1442
+ }),
1443
+ ),
1444
+ react.createElement(
1445
+ "div", { className: "dsh-mem-rb-muted", style: { marginTop: 4 } },
1446
+ st.source === "off" ? "当前:关键词(BM25)检索,不做向量嵌入"
1447
+ : st.source === "remote" ? "当前:远程嵌入(" + (rt ? "" : "") + "模型由部署配置给定)"
1448
+ : "当前:本地嵌入" + (st.activeModel ? "(" + st.activeModel + ")" : ""),
1449
+ ),
1450
+ st.activeNote
1451
+ ? react.createElement("div", { style: { marginTop: 4, fontSize: 12, color: "var(--dsh-mem-danger)" } }, st.activeNote)
1452
+ : null,
1453
+ err && err !== "__unsupported__"
1454
+ ? react.createElement("div", { style: { marginTop: 6, fontSize: 12, color: "var(--dsh-mem-danger)" } }, err)
1455
+ : null,
1456
+ dl && dl.phase === "error"
1457
+ ? react.createElement("div", { style: { marginTop: 6, fontSize: 12, color: "var(--dsh-mem-danger)" } }, "下载失败:" + (dl.error || ""))
1458
+ : null,
1459
+ runtimeRow,
1460
+ applyRow,
1461
+ react.createElement("div", { style: S.panelLabel }, "本地模型目录(下载后离线可用,不随插件分发)"),
1462
+ modelCards,
1463
+ );
1464
+ }
1465
+
668
1466
  // ── Tab:概览(开关面板 + 计数) ──
669
1467
  function OverviewTab(props) {
670
1468
  var rpc = props.rpc;
@@ -721,22 +1519,27 @@ window.__ModuleLoader__.load({
721
1519
  });
722
1520
  };
723
1521
 
724
- var rows = [];
1522
+ // 统计分两段:计数瓦片(一眼可读)+ 明细行(目录/版本/时间线/队列)
1523
+ var tiles = [];
1524
+ var infos = [];
725
1525
  if (stats) {
726
- rows.push(["数据目录", stats.dataDir]);
727
- rows.push(["插件版本", "v" + stats.version]);
728
- rows.push(["默认档", modeLabel(stats.family)]);
729
- rows.push(["L0 今日消息", String(stats.l0Today)]);
730
- rows.push(["L1 原子记忆", stats.l1Count + " 条(累计抽取 " + stats.l1TotalExtracted + ")"]);
731
- rows.push(["L2 场景块", String(stats.sceneCount)]);
732
- rows.push(["L3 画像", stats.personaChars > 0 ? stats.personaChars + " 字符" : "未生成"]);
733
- rows.push(["上次 L1 抽取", fmtTime(stats.lastExtractAt)]);
734
- rows.push(["上次 L2 整合", fmtTime(stats.lastL2At)]);
735
- rows.push(["上次 L3 蒸馏", fmtTime(stats.lastL3At)]);
736
- rows.push([" L2 新记忆", stats.memoriesSinceL2 + " / 5"]);
737
- rows.push([" L3 新记忆", stats.memoriesSinceL3 + " / 20"]);
738
- rows.push(["待重试消息", String(stats.pendingExtract)]);
1526
+ var th = stats.thresholds || {};
1527
+ tiles.push({ num: String(stats.l0Today), label: "L0 今日消息" });
1528
+ tiles.push({ num: String(stats.l1Count), label: "L1 原子记忆" });
1529
+ tiles.push({ num: String(stats.sceneCount), label: "L2 场景块" });
1530
+ tiles.push({ num: String(stats.pendingExtract), label: "待重试消息" });
1531
+ infos.push(["数据目录", stats.dataDir]);
1532
+ infos.push(["插件版本", "v" + stats.version]);
1533
+ infos.push(["默认档", modeLabel(stats.family)]);
1534
+ infos.push(["L1 累计抽取", String(stats.l1TotalExtracted)]);
1535
+ infos.push(["L3 画像", stats.personaChars > 0 ? stats.personaChars + " 字符" : "未生成"]);
1536
+ infos.push(["上次 L1 抽取", fmtTime(stats.lastExtractAt)]);
1537
+ infos.push(["上次 L2 整合", fmtTime(stats.lastL2At)]);
1538
+ infos.push(["上次 L3 蒸馏", fmtTime(stats.lastL3At)]);
1539
+ infos.push(["待 L2 新记忆", stats.memoriesSinceL2 + " / " + (th.l2MinNewMemories != null ? th.l2MinNewMemories : 5)]);
1540
+ infos.push(["待 L3 新记忆", stats.memoriesSinceL3 + " / " + (th.l3Interval != null ? th.l3Interval : 20)]);
739
1541
  }
1542
+ var degraded = stats && stats.message && stats.message !== "running";
740
1543
 
741
1544
  var master = settingsData && settingsData.settings ? settingsData.settings.enabled : true;
742
1545
  var ceilingNote = "";
@@ -812,23 +1615,41 @@ window.__ModuleLoader__.load({
812
1615
  : null,
813
1616
  )
814
1617
  : null,
1618
+ react.createElement(EmbeddingSection, { rpc: rpc }),
1619
+ react.createElement(RebuildPanel, { rpc: rpc }),
1620
+ degraded
1621
+ ? react.createElement(
1622
+ "div",
1623
+ { style: Object.assign({}, S.error, { marginBottom: 10 }) },
1624
+ "⚠ " + stats.message + "。上方数据为最后一次成功读取的值,记忆功能当前未工作。",
1625
+ )
1626
+ : null,
815
1627
  error
816
1628
  ? react.createElement("div", { style: S.error }, "获取状态失败:" + error)
817
1629
  : !stats
818
1630
  ? react.createElement("p", { style: S.intro }, "正在读取记忆状态…")
819
1631
  : react.createElement(
820
- "table",
821
- { style: S.table },
1632
+ "div", null,
1633
+ react.createElement("div", { style: S.panelLabel }, "记忆概况"),
822
1634
  react.createElement(
823
- "tbody", null,
824
- rows.map(function (row) {
1635
+ "div", { style: S.statGrid },
1636
+ tiles.map(function (t) {
825
1637
  return react.createElement(
826
- "tr", { key: row[0] },
827
- react.createElement("td", { style: S.tdKey }, row[0]),
828
- react.createElement("td", { style: S.td }, row[1]),
1638
+ "div",
1639
+ { key: t.label, className: "dsh-mem-card", style: S.statTile },
1640
+ react.createElement("div", { style: S.statNum }, t.num),
1641
+ react.createElement("div", { style: S.statLabel }, t.label),
829
1642
  );
830
1643
  }),
831
1644
  ),
1645
+ react.createElement("div", { style: S.panelLabel }, "运行状态"),
1646
+ infos.map(function (row) {
1647
+ return react.createElement(
1648
+ "div", { key: row[0], style: S.infoRow },
1649
+ react.createElement("span", { style: S.infoKey }, row[0]),
1650
+ react.createElement("span", { style: S.infoVal }, row[1]),
1651
+ );
1652
+ }),
832
1653
  ),
833
1654
  react.createElement(
834
1655
  "p", { style: S.hint },
@@ -857,6 +1678,9 @@ window.__ModuleLoader__.load({
857
1678
  var loadingState = react.useState(false);
858
1679
  var loading = loadingState[0];
859
1680
  var setLoading = loadingState[1];
1681
+ var truncState = react.useState(false);
1682
+ var truncated = truncState[0];
1683
+ var setTruncated = truncState[1];
860
1684
  var errState = react.useState(null);
861
1685
  var error = errState[0];
862
1686
  var setError = errState[1];
@@ -879,15 +1703,20 @@ window.__ModuleLoader__.load({
879
1703
  var last = lastState[0];
880
1704
  var setLast = lastState[1];
881
1705
 
1706
+ // 请求序列号:快速连续搜索/翻页时丢弃过期响应(慢的旧响应不得覆盖新结果)
1707
+ var seqRef = react.useRef(0);
1708
+
882
1709
  var fetchPage = react.useCallback(function (conds, offset, append) {
883
1710
  setLoading(true);
884
1711
  setError(null);
1712
+ var token = ++seqRef.current;
885
1713
  var payload = { limit: limit, offset: offset };
886
1714
  if (conds.query) payload.query = conds.query;
887
1715
  if (conds.type) payload.type = conds.type;
888
1716
  if (conds.scene) payload.scene = conds.scene;
889
1717
  rpc("dsh-memory/list-records", payload)
890
1718
  .then(function (r) {
1719
+ if (token !== seqRef.current) return;
891
1720
  setLoading(false);
892
1721
  if (!r || !r.ok) {
893
1722
  setError(r && r.error ? r.error.message : "RPC error");
@@ -897,9 +1726,11 @@ window.__ModuleLoader__.load({
897
1726
  setItems(function (prev) { return append ? prev.concat(v.items) : v.items; });
898
1727
  setHasMore(!!v.hasMore);
899
1728
  setTotal(v.total === undefined || v.total === null ? null : v.total);
1729
+ setTruncated(!!v.truncated);
900
1730
  if (v.scenes) setSceneOptions(v.scenes);
901
1731
  })
902
1732
  .catch(function (e) {
1733
+ if (token !== seqRef.current) return;
903
1734
  setLoading(false);
904
1735
  setError(String((e && e.message) || e));
905
1736
  });
@@ -927,8 +1758,8 @@ window.__ModuleLoader__.load({
927
1758
  "div", null,
928
1759
  react.createElement(
929
1760
  "div", { style: S.toolbar },
930
- react.createElement("input", {
931
- style: S.input,
1761
+ react.createElement(NInput, {
1762
+ style: { flex: 1, minWidth: 160 },
932
1763
  placeholder: "搜索记忆内容(BM25 关键词)…",
933
1764
  value: query,
934
1765
  onChange: function (e) { setQuery(e.target.value); },
@@ -937,7 +1768,7 @@ window.__ModuleLoader__.load({
937
1768
  react.createElement(
938
1769
  "select",
939
1770
  {
940
- style: S.select,
1771
+ className: "dsh-mem-select",
941
1772
  value: typeFilter,
942
1773
  onChange: function (e) { setTypeFilter(e.target.value); },
943
1774
  },
@@ -949,7 +1780,7 @@ window.__ModuleLoader__.load({
949
1780
  react.createElement(
950
1781
  "select",
951
1782
  {
952
- style: S.select,
1783
+ className: "dsh-mem-select",
953
1784
  value: sceneFilter,
954
1785
  onChange: function (e) { setSceneFilter(e.target.value); },
955
1786
  },
@@ -958,18 +1789,24 @@ window.__ModuleLoader__.load({
958
1789
  return react.createElement("option", { key: s, value: s }, s.length > 24 ? s.slice(0, 24) + "…" : s);
959
1790
  }),
960
1791
  ),
961
- react.createElement("button", { style: S.button, onClick: search }, "搜索"),
1792
+ react.createElement(NButton, { onClick: search }, "搜索"),
962
1793
  ),
963
1794
  react.createElement(
964
1795
  "div", { style: Object.assign({}, S.flexRow, { marginBottom: 10 }) },
965
1796
  react.createElement("span", { style: S.muted }, loading ? "加载中…" : countText),
966
1797
  react.createElement("div", { style: S.grow }),
967
- react.createElement("button", {
968
- style: S.button,
1798
+ react.createElement(NButton, {
969
1799
  onClick: function () { fetchPage(last, 0, false); },
970
1800
  }, "刷新"),
971
1801
  ),
972
1802
  error ? react.createElement("div", { style: S.error }, error) : null,
1803
+ truncated
1804
+ ? react.createElement(
1805
+ "div",
1806
+ { style: S.hint },
1807
+ "搜索分页已达检索上限(200 条),更早的结果未显示。请用更精确的关键词或类型/情境过滤。",
1808
+ )
1809
+ : null,
973
1810
  items.length === 0 && !loading && !error
974
1811
  ? react.createElement("p", { style: S.intro }, "暂无记忆。对话几轮后,蒸馏管线会自动抽取记忆。")
975
1812
  : items.map(function (m) {
@@ -978,18 +1815,15 @@ window.__ModuleLoader__.load({
978
1815
  "div",
979
1816
  {
980
1817
  key: m.id,
981
- style: S.card,
1818
+ className: "dsh-mem-card dsh-mem-card-hover",
1819
+ style: Object.assign({}, S.card, { cursor: "pointer" }),
982
1820
  onClick: function () { setExpandedId(open ? null : m.id); },
983
1821
  },
984
1822
  react.createElement(
985
1823
  "div", { style: S.cardHead },
986
1824
  react.createElement(
987
1825
  "span",
988
- {
989
- style: Object.assign({}, S.tag, {
990
- background: TYPE_COLORS[m.type] || "#666",
991
- }),
992
- },
1826
+ { className: "dsh-mem-tag dsh-mem-tag-" + m.type },
993
1827
  TYPE_LABELS[m.type] || m.type,
994
1828
  ),
995
1829
  react.createElement("span", { style: S.muted }, "优先级 " + m.priority),
@@ -1004,13 +1838,13 @@ window.__ModuleLoader__.load({
1004
1838
  ? react.createElement(
1005
1839
  "div", { style: S.detail },
1006
1840
  "id: " + m.id + "\n" +
1007
- "情境: " + (m.scene || "") + "\n" +
1841
+ "情境: " + (m.scene || "-") + "\n" +
1008
1842
  "版本: v" + m.version + "(去重合并次数 " + m.version + ")\n" +
1009
1843
  "创建: " + fmtTime(m.createdAt) + "\n" +
1010
- "活跃时间: " + (m.timestamps && m.timestamps.length > 0 ? m.timestamps.map(fmtTime).join(" → ") : "") + "\n" +
1844
+ "活跃时间: " + (m.timestamps && m.timestamps.length > 0 ? m.timestamps.map(fmtTime).join(" → ") : "-") + "\n" +
1011
1845
  (m.sourceMessageIds && m.sourceMessageIds.length > 0
1012
1846
  ? "来源消息: " + m.sourceMessageIds.join(", ")
1013
- : "来源消息: "),
1847
+ : "来源消息: -"),
1014
1848
  )
1015
1849
  : null,
1016
1850
  );
@@ -1019,9 +1853,11 @@ window.__ModuleLoader__.load({
1019
1853
  ? react.createElement(
1020
1854
  "div", { style: S.flexRow },
1021
1855
  react.createElement("div", { style: S.grow }),
1022
- react.createElement("button", {
1023
- style: S.button,
1024
- onClick: function () { fetchPage(last, items.length, true); },
1856
+ react.createElement(NButton, {
1857
+ disabled: loading,
1858
+ onClick: function () {
1859
+ if (!loading) fetchPage(last, items.length, true);
1860
+ },
1025
1861
  }, loading ? "加载中…" : "加载更多"),
1026
1862
  )
1027
1863
  : null,
@@ -1055,7 +1891,7 @@ window.__ModuleLoader__.load({
1055
1891
  "div", { style: Object.assign({}, S.flexRow, { marginBottom: 10 }) },
1056
1892
  react.createElement("span", { style: S.muted }, items ? items.length + " 个场景块" : "加载中…"),
1057
1893
  react.createElement("div", { style: S.grow }),
1058
- react.createElement("button", { style: S.button, onClick: load }, "刷新"),
1894
+ react.createElement(NButton, { onClick: load }, "刷新"),
1059
1895
  ),
1060
1896
  error ? react.createElement("div", { style: S.error }, error) : null,
1061
1897
  items && items.length === 0
@@ -1063,7 +1899,7 @@ window.__ModuleLoader__.load({
1063
1899
  : null,
1064
1900
  (items || []).map(function (s) {
1065
1901
  return react.createElement(
1066
- "div", { key: s.path, style: S.card },
1902
+ "div", { key: s.path, className: "dsh-mem-card", style: S.card },
1067
1903
  react.createElement(
1068
1904
  "div", { style: S.sceneHead },
1069
1905
  react.createElement("span", { style: S.sceneTitle }, s.path),
@@ -1090,11 +1926,18 @@ window.__ModuleLoader__.load({
1090
1926
  var contentState = react.useState(null);
1091
1927
  var content = contentState[0];
1092
1928
  var setContent = contentState[1];
1929
+ var errState = react.useState(null);
1930
+ var error = errState[0];
1931
+ var setError = errState[1];
1093
1932
 
1094
1933
  var load = react.useCallback(function () {
1934
+ setError(null);
1095
1935
  rpc("dsh-memory/persona", {})
1096
- .then(function (r) { if (r && r.ok) setContent(r.value.content); })
1097
- .catch(function () {});
1936
+ .then(function (r) {
1937
+ if (r && r.ok) setContent(r.value.content);
1938
+ else setError(r && r.error ? r.error.message : "RPC error");
1939
+ })
1940
+ .catch(function (e) { setError(String((e && e.message) || e)); });
1098
1941
  }, [rpc]);
1099
1942
 
1100
1943
  react.useEffect(function () { load(); }, [load]);
@@ -1104,10 +1947,17 @@ window.__ModuleLoader__.load({
1104
1947
  react.createElement(
1105
1948
  "div", { style: Object.assign({}, S.flexRow, { marginBottom: 10 }) },
1106
1949
  react.createElement("span", { style: S.muted },
1107
- content === null ? "加载中…" : content ? content.length + " 字符" : "未生成画像"),
1950
+ error ? "加载失败" : content === null ? "加载中…" : content ? content.length + " 字符" : "未生成画像"),
1108
1951
  react.createElement("div", { style: S.grow }),
1109
- react.createElement("button", { style: S.button, onClick: load }, "刷新"),
1952
+ react.createElement(NButton, { onClick: load }, "刷新"),
1110
1953
  ),
1954
+ error
1955
+ ? react.createElement(
1956
+ "div",
1957
+ { style: Object.assign({}, S.error, { marginBottom: 10 }) },
1958
+ "画像读取失败:" + error + "(点右上“刷新”重试)",
1959
+ )
1960
+ : null,
1111
1961
  content
1112
1962
  ? react.createElement("pre", { style: S.pre }, content)
1113
1963
  : content === null
@@ -1122,11 +1972,18 @@ window.__ModuleLoader__.load({
1122
1972
  var linesState = react.useState(null);
1123
1973
  var lines = linesState[0];
1124
1974
  var setLines = linesState[1];
1975
+ var errState = react.useState(null);
1976
+ var error = errState[0];
1977
+ var setError = errState[1];
1125
1978
 
1126
1979
  var load = react.useCallback(function () {
1980
+ setError(null);
1127
1981
  rpc("dsh-memory/log-tail", { lines: 200 })
1128
- .then(function (r) { if (r && r.ok) setLines(r.value.lines); })
1129
- .catch(function () { setLines([]); });
1982
+ .then(function (r) {
1983
+ if (r && r.ok) setLines(r.value.lines);
1984
+ else setError(r && r.error ? r.error.message : "RPC error");
1985
+ })
1986
+ .catch(function (e) { setError(String((e && e.message) || e)); });
1130
1987
  }, [rpc]);
1131
1988
 
1132
1989
  react.useEffect(function () { load(); }, [load]);
@@ -1135,11 +1992,17 @@ window.__ModuleLoader__.load({
1135
1992
  "div", null,
1136
1993
  react.createElement(
1137
1994
  "div", { style: Object.assign({}, S.flexRow, { marginBottom: 10 }) },
1138
- react.createElement("span", { style: S.muted }, lines === null ? "加载中…" : "最近 " + lines.length + " 行(memory.log)"),
1995
+ react.createElement("span", { style: S.muted }, error ? "加载失败" : lines === null ? "加载中…" : "最近 " + lines.length + " 行(memory.log)"),
1139
1996
  react.createElement("div", { style: S.grow }),
1140
- react.createElement("button", { style: S.button, onClick: load }, "刷新"),
1997
+ react.createElement(NButton, { onClick: load }, "刷新"),
1141
1998
  ),
1142
- react.createElement("pre", { style: S.pre }, (lines || []).join("\n") || "(暂无日志)"),
1999
+ error
2000
+ ? react.createElement(
2001
+ "div",
2002
+ { style: Object.assign({}, S.error, { marginBottom: 10 }) },
2003
+ "日志读取失败:" + error + "(点右上“刷新”重试)",
2004
+ )
2005
+ : react.createElement("pre", { style: S.pre }, (lines || []).join("\n") || "(暂无日志)"),
1143
2006
  );
1144
2007
  }
1145
2008
 
@@ -1152,12 +2015,66 @@ window.__ModuleLoader__.load({
1152
2015
  ["log", "日志"],
1153
2016
  ];
1154
2017
 
2018
+ // ── 设置页侧边栏 icon:宿主 navIcon() 按 section id 硬编码白名单(slot 注册对象
2019
+ // 无 icon 字段),"dsh-memory" 落齿轮兜底。受控 DOM 补丁:把侧边栏"记忆"按钮的
2020
+ // 齿轮换成书本 icon。宿主只在分节激活时渲染我们的面板,所以补丁不能依赖
2021
+ // MemoryPanel 挂载——由常驻的输入栏 pill 驱动一个 body 级防抖观察器(全局单例,
2022
+ // 应用生命周期存续,观察 body 子树 childList):设置页随时打开、侧边栏随时
2023
+ // 重渲染都能打上/重打补丁。找不到或 DOM 结构变化时静默保持原生齿轮。 ──
2024
+ var BOOK_ICON_SVG =
2025
+ '<svg data-mem-icon="1" viewBox="0 0 16 16" width="16" height="16" fill="none" ' +
2026
+ 'xmlns="http://www.w3.org/2000/svg" style="flex-shrink:0">' +
2027
+ '<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" ' +
2028
+ 'stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/>' +
2029
+ '<path d="M8 3.4v9.3" stroke="currentColor" stroke-width="1.2"/></svg>';
2030
+
2031
+ function patchSidebarIcon() {
2032
+ try {
2033
+ var buttons = document.querySelectorAll("button");
2034
+ for (var i = 0; i < buttons.length; i++) {
2035
+ var b = buttons[i];
2036
+ var span = b.querySelector("span");
2037
+ var svg = b.querySelector("svg");
2038
+ // 侧边栏导航项 = [svg 图标, span 标签文本];输入栏 pill 的文本形态不同("记忆 · X")
2039
+ if (span && svg && span.textContent.trim() === "记忆" && svg.getAttribute("data-mem-icon") !== "1") {
2040
+ svg.outerHTML = BOOK_ICON_SVG;
2041
+ }
2042
+ }
2043
+ } catch (e) {
2044
+ /* best-effort:失败保持原生齿轮 */
2045
+ }
2046
+ }
2047
+
2048
+ // body 子树变更频繁(对话流式渲染),补丁扫描走 250ms 尾随防抖
2049
+ var sidebarIconTimer = null;
2050
+ function scheduleSidebarPatch() {
2051
+ if (sidebarIconTimer !== null) return;
2052
+ sidebarIconTimer = setTimeout(function () {
2053
+ sidebarIconTimer = null;
2054
+ patchSidebarIcon();
2055
+ }, 250);
2056
+ }
2057
+
2058
+ function watchSidebarIcon() {
2059
+ patchSidebarIcon();
2060
+ try {
2061
+ if (document.body.getAttribute("data-mem-icon-bodywatch") === "1") return;
2062
+ document.body.setAttribute("data-mem-icon-bodywatch", "1");
2063
+ new MutationObserver(scheduleSidebarPatch).observe(document.body, { childList: true, subtree: true });
2064
+ } catch (e) {
2065
+ /* ignore */
2066
+ }
2067
+ }
2068
+
1155
2069
  function MemoryPanel(props) {
1156
2070
  var rpc = props.rpc;
1157
2071
  var tabState = react.useState("overview");
1158
2072
  var tab = tabState[0];
1159
2073
  var setTab = tabState[1];
1160
2074
 
2075
+ ensureThemeStyle(); // 设置页挂载即注入主题令牌与组件样式
2076
+ react.useEffect(function () { watchSidebarIcon(); }, []);
2077
+
1161
2078
  var body;
1162
2079
  if (tab === "overview") body = react.createElement(OverviewTab, { rpc: rpc });
1163
2080
  else if (tab === "records") body = react.createElement(RecordsTab, { rpc: rpc });
@@ -1167,7 +2084,7 @@ window.__ModuleLoader__.load({
1167
2084
 
1168
2085
  return react.createElement(
1169
2086
  "div",
1170
- { style: S.section },
2087
+ { className: "dsh-mem-root", style: S.section },
1171
2088
  react.createElement("h2", { style: S.heading }, "记忆 (Memory)"),
1172
2089
  react.createElement(
1173
2090
  "p", { style: S.intro },
@@ -1180,7 +2097,7 @@ window.__ModuleLoader__.load({
1180
2097
  "button",
1181
2098
  {
1182
2099
  key: t[0],
1183
- style: Object.assign({}, S.tab, tab === t[0] ? S.tabActive : null),
2100
+ className: tab === t[0] ? "dsh-mem-tab dsh-mem-tab-on" : "dsh-mem-tab",
1184
2101
  onClick: function () { setTab(t[0]); },
1185
2102
  },
1186
2103
  t[1],