dsh-layered-memory 0.6.1 → 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 (48) hide show
  1. package/README.en.md +101 -81
  2. package/README.md +88 -80
  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 +1100 -402
  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 +107 -44
  19. package/dist/pipeline/rebuild.d.ts +2 -0
  20. package/dist/pipeline/rebuild.js +7 -0
  21. package/dist/pipeline/runner.d.ts +7 -1
  22. package/dist/pipeline/runner.js +22 -7
  23. package/dist/settings.js +79 -20
  24. package/dist/stats.d.ts +7 -1
  25. package/dist/stats.js +131 -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/runtime-installer.d.ts +60 -0
  41. package/dist/store/runtime-installer.js +181 -0
  42. package/dist/store/sqlite.d.ts +53 -7
  43. package/dist/store/sqlite.js +339 -72
  44. package/dist/tools/index.js +6 -4
  45. package/dist/util/filelog.d.ts +2 -0
  46. package/dist/util/filelog.js +20 -3
  47. package/package.json +1 -1
  48. 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",
166
+ marginBottom: 14,
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,
161
178
  marginBottom: 14,
162
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,149 +593,305 @@ 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 放不了
496
- // @keyframes/@property/媒体查询,惰性注入一次性样式表(id 防重复)。
497
- // dsw 主题变量参考其前端令牌:暗色走 body[data-ds-dark-theme] ──
498
- var FLOW_STYLE_ID = "dsh-mem-flow-style";
499
- function ensureFlowStyle() {
500
- 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;
501
691
  var el = document.createElement("style");
502
- el.id = FLOW_STYLE_ID;
692
+ el.id = THEME_STYLE_ID;
503
693
  el.textContent = [
504
- // conic 角度动画需要 @property 注册才能插值;不支持的浏览器优雅降级为静态渐变边框
694
+ // conic 角度动画需要 @property 注册才能插值;不支持 @property 的浏览器里
695
+ // var(--dsh-mem-angle) 无定义 → conic 层失效 → 整条 background 退化为无背景
696
+ // (光带边框与内底一并消失,仅剩文字色)。2026 常青浏览器均已支持,仅作记录。
505
697
  "@property --dsh-mem-angle { syntax: '<angle>'; initial-value: 0deg; inherits: false; }",
506
698
  "@keyframes dshMemFlow { to { --dsh-mem-angle: 360deg; } }",
507
- // 边缘流光(双层背景):border 区画旋转 conic 冷蓝光带;内部必须是【不透明】底色盖住
508
- // 光带(半透明内层会让 conic 透进按钮内部,文字被光斑干扰——实测事故)。
509
- // 不透明底 = 主题底混 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)
510
820
  ".dsh-mem-flow {",
511
821
  " border: 1px solid transparent;",
512
822
  " background:",
513
823
  " linear-gradient(",
514
- " color-mix(in srgb, var(--dsw-alias-bg-layer-2, #ffffff) 88%, #3b82f6),",
515
- " 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))",
516
826
  " ) padding-box,",
517
827
  " conic-gradient(from var(--dsh-mem-angle),",
518
- " rgba(8,145,178,0.9), rgba(59,130,246,0.9), rgba(125,211,252,1),",
519
- " 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;",
520
830
  " animation: dshMemFlow 3s linear infinite;",
521
- " color: #0284c7;",
522
831
  "}",
523
- "body[data-ds-dark-theme] .dsh-mem-flow { color: #7dd3fc; }",
524
- "@media (prefers-reduced-motion: reduce) { .dsh-mem-flow { animation: none; } }",
525
- // 浮层玻璃材质(Apple 菜单配方):材质层必须是【无 transform 的独立元素】——
526
- // Chromium transform 元素上的 backdrop-filter 采样异常(玻璃失效,实测)。
527
- // 低不透明度基底让底下内容透出 + 顶部光泽渐变 + blur/saturate + 亮缘 + 分主题阴影
528
- ".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 {",
529
841
  " border-radius: 12px;",
530
- " background:",
531
- " linear-gradient(rgba(255,255,255,0.34), rgba(255,255,255,0.05)),",
532
- " rgba(248,249,251,0.55);",
533
- " -webkit-backdrop-filter: blur(32px) saturate(180%);",
534
- " backdrop-filter: blur(32px) saturate(180%);",
535
- " border: 1px solid rgba(255,255,255,0.55);",
536
- " box-shadow:",
537
- " 0 12px 40px rgba(0,0,0,0.16), 0 2px 8px rgba(0,0,0,0.07),",
538
- " 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);",
539
846
  "}",
540
- "body[data-ds-dark-theme] .dsh-mem-glass {",
541
- " background:",
542
- " linear-gradient(rgba(255,255,255,0.09), rgba(255,255,255,0.015)),",
543
- " rgba(30,32,40,0.55);",
544
- " border-color: rgba(255,255,255,0.14);",
545
- " box-shadow:",
546
- " 0 16px 48px rgba(0,0,0,0.55), 0 3px 12px rgba(0,0,0,0.35),",
547
- " 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);",
548
858
  "}",
549
- "@media (prefers-reduced-transparency: reduce) {",
550
- " .dsh-mem-glass { background: var(--dsw-alias-bg-layer-2, #ffffff); backdrop-filter: none; -webkit-backdrop-filter: none; }",
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);",
551
866
  "}",
552
- // ── 重建面板(Light/Dark 双主题:基底用 dsw 真实令牌 bg-layer-2/border-l2,
553
- // 暗色由 body[data-ds-dark-theme] 切换;强调红/进度蓝紫双主题各调一档) ──
554
- ".dsh-mem-rb-card {",
555
- " border: 1px solid var(--dsw-alias-border-l2, #e5e7eb);",
556
- " border-radius: 10px;",
557
- " background: var(--dsw-alias-bg-layer-2, #ffffff);",
558
- " padding: 12px 14px;",
559
- " margin-bottom: 14px;",
560
- " font-size: 13px;",
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);",
561
872
  "}",
562
- ".dsh-mem-rb-btn {",
563
- " padding: 5px 14px; font-size: 13px; border-radius: 6px; cursor: pointer;",
564
- " border: 1px solid var(--dsw-alias-border-l2, #d0d7de);",
565
- " background: var(--dsw-alias-bg-layer-2, #ffffff);",
566
- " color: inherit;",
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;",
567
879
  "}",
568
- ".dsh-mem-rb-btn:disabled { opacity: 0.45; cursor: not-allowed; }",
569
- ".dsh-mem-rb-danger { border-color: rgba(207,34,46,0.45); color: #cf222e; background: transparent; }",
570
- "body[data-ds-dark-theme] .dsh-mem-rb-danger { color: #f4707b; border-color: rgba(244,112,123,0.5); }",
571
- ".dsh-mem-rb-primary { background: #cf222e; border-color: #cf222e; color: #fff; }",
572
- "body[data-ds-dark-theme] .dsh-mem-rb-primary { background: #b62324; border-color: #b62324; }",
573
- ".dsh-mem-rb-bar { height: 8px; border-radius: 4px; overflow: hidden; flex: 1;",
574
- " background: var(--dsw-alias-border-l2, #e8ebef); }",
575
- "body[data-ds-dark-theme] .dsh-mem-rb-bar { background: rgba(255,255,255,0.12); }",
576
- ".dsh-mem-rb-fill { height: 100%; border-radius: 4px;",
577
- " background: linear-gradient(90deg, #0969da, #8250df); transition: width .4s ease; }",
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; }",
578
882
  ".dsh-mem-rb-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.35);",
579
883
  " display: flex; align-items: center; justify-content: center; z-index: 2000; }",
580
884
  ".dsh-mem-rb-modal { width: 440px; max-width: calc(100vw - 48px); border-radius: 12px;",
581
885
  " padding: 18px 20px; font-size: 13px; line-height: 1.6;",
582
- " border: 1px solid var(--dsw-alias-border-l2, #e5e7eb);",
583
- " background: var(--dsw-alias-bg-layer-2, #ffffff);",
886
+ " border: 1px solid var(--dsh-mem-border); background: var(--dsh-mem-bg-card); color: var(--dsh-mem-text-1);",
584
887
  " box-shadow: 0 16px 48px rgba(0,0,0,0.24); }",
585
888
  "body[data-ds-dark-theme] .dsh-mem-rb-modal { box-shadow: 0 16px 48px rgba(0,0,0,0.6); }",
586
- ".dsh-mem-rb-muted { font-size: 12px; color: var(--dsw-alias-label-caption, #6e7781); }",
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; }",
894
+ "}",
587
895
  ].join("\n");
588
896
  document.head.appendChild(el);
589
897
  }
@@ -602,16 +910,31 @@ window.__ModuleLoader__.load({
602
910
  var open = openState[0];
603
911
  var setOpen = openState[1];
604
912
  var wrapRef = react.useRef(null);
913
+ // 请求序列号:快速切换会话时丢弃旧会话的过期响应(慢响应不得覆盖新会话档位)
914
+ var seqRef = react.useRef(0);
605
915
 
606
- react.useEffect(function () {
916
+ var load = react.useCallback(function () {
607
917
  if (!sessionId || !rpc) return;
918
+ var token = ++seqRef.current;
919
+ setError(null);
608
920
  rpc("dsh-memory/session-mode-get", { sessionId: sessionId })
609
921
  .then(function (r) {
922
+ if (token !== seqRef.current) return;
610
923
  if (r && r.ok && r.value) setMode(r.value.mode);
924
+ else setError(r && r.error ? r.error.message : "RPC error");
611
925
  })
612
- .catch(function () {});
926
+ .catch(function (e) {
927
+ if (token !== seqRef.current) return;
928
+ setError(String((e && e.message) || e));
929
+ });
613
930
  }, [sessionId, rpc]);
614
931
 
932
+ react.useEffect(function () { load(); }, [load]);
933
+
934
+ // 侧边栏书本 icon 补丁由常驻 pill 驱动(MemoryPanel 只在记忆分节激活时挂载,
935
+ // 覆盖不了"打开设置第一眼"的场景);body 级观察器全局单例,多实例幂等
936
+ react.useEffect(function () { watchSidebarIcon(); }, []);
937
+
615
938
  react.useEffect(function () {
616
939
  if (!open) return;
617
940
  var onDown = function (e) {
@@ -650,12 +973,13 @@ window.__ModuleLoader__.load({
650
973
  if (!sessionId || !rpc) return null;
651
974
  var info = modeInfo(mode);
652
975
  var loaded = mode !== null;
653
- var isAuto = loaded && mode === "auto";
976
+ // 关闭档与其余三档二分:关闭 = dsh 透明按钮(无边框无底无光晕);
977
+ // 日常/工作/智能 = 同款流光 + 光晕,档位区分靠蓝阶文字色与流光内底混色深度
978
+ var isOff = loaded && mode === "off";
979
+ var isFlow = loaded && !isOff;
654
980
 
655
- if (isAuto) ensureFlowStyle();
981
+ ensureThemeStyle();
656
982
 
657
- // auto 档的边框/背景/文字色均由 .dsh-mem-flow 提供(双层背景画流光边 + 分主题文字),
658
- // inline 不能设置这些——inline 优先级会盖掉 class 里的流光
659
983
  var pillStyle = {
660
984
  display: "inline-flex",
661
985
  alignItems: "center",
@@ -667,13 +991,13 @@ window.__ModuleLoader__.load({
667
991
  fontWeight: 500,
668
992
  lineHeight: "20px",
669
993
  cursor: "pointer",
994
+ // 流光档的边框/背景由 .dsh-mem-flow 的双层背景提供(流光边 + 不透明内底),
995
+ // inline 只给文字色 / 光晕 / 流光内底混色通道(--dsh-mem-pill-tint)
996
+ color: isFlow ? info.color : "var(--dsh-mem-text-2)",
670
997
  };
671
- if (isAuto) {
672
- pillStyle.boxShadow = "0 0 12px rgba(56,189,248,0.30)";
673
- } else {
674
- pillStyle.border = "1px solid " + (loaded ? info.color + "66" : "var(--dsw-alias-border-secondary, #ccc)");
675
- pillStyle.background = loaded ? info.color + "14" : "var(--dsw-alias-bg-secondary, #f6f8fa)";
676
- 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;
677
1001
  }
678
1002
 
679
1003
  return react.createElement(
@@ -683,13 +1007,16 @@ window.__ModuleLoader__.load({
683
1007
  "button",
684
1008
  {
685
1009
  type: "button",
686
- title: "本会话记忆档位(点击切换)",
687
- onClick: function () { setOpen(!open); },
688
- 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",
689
1016
  style: pillStyle,
690
1017
  },
691
- "记忆·",
692
- react.createElement("span", null, loaded ? info.label : "…"),
1018
+ "记忆 · ",
1019
+ react.createElement("span", null, loaded ? info.label : error ? "⚠" : "…"),
693
1020
  ),
694
1021
  open
695
1022
  ? react.createElement(ModeSlider, {
@@ -742,7 +1069,7 @@ window.__ModuleLoader__.load({
742
1069
  }, [running, refresh]);
743
1070
 
744
1071
  if (!rb || rb.supported === false) return null;
745
- ensureFlowStyle();
1072
+ ensureThemeStyle();
746
1073
 
747
1074
  var start = function () {
748
1075
  setBusy(true);
@@ -785,7 +1112,7 @@ window.__ModuleLoader__.load({
785
1112
  } else if (!running && rb.phase === "cancelled") {
786
1113
  lastNote = "上次重建:已取消(完成 " + rb.done + "/" + rb.total + " 会话,已重建部分保留)";
787
1114
  } else if (!running && rb.phase === "failed") {
788
- lastNote = "上次重建:失败 — " + (rb.error || "未知错误");
1115
+ lastNote = "上次重建:失败:" + (rb.error || "未知错误");
789
1116
  }
790
1117
 
791
1118
  return react.createElement(
@@ -800,17 +1127,14 @@ window.__ModuleLoader__.load({
800
1127
  : "从 L0 原始对话重新蒸馏 L1/L2/L3;旧数据先归档(不删除)",
801
1128
  ),
802
1129
  running
803
- ? react.createElement("button", {
804
- type: "button",
805
- className: "dsh-mem-rb-btn",
1130
+ ? react.createElement(NButton, {
806
1131
  disabled: busy || rb.cancelRequested,
807
1132
  onClick: cancel,
808
1133
  }, rb.cancelRequested ? "取消中…" : "取消重建")
809
- : react.createElement("button", {
810
- type: "button",
811
- className: "dsh-mem-rb-btn dsh-mem-rb-danger",
1134
+ : react.createElement(NButton, {
812
1135
  disabled: busy || empty,
813
1136
  title: empty ? "L0 无消息,无可重建内容" : "重新蒸馏全部记忆",
1137
+ style: { color: "var(--dsh-mem-danger)" },
814
1138
  onClick: function () { setConfirmOpen(true); },
815
1139
  }, busy ? "…" : "开始重建"),
816
1140
  ),
@@ -831,58 +1155,314 @@ window.__ModuleLoader__.load({
831
1155
  ? react.createElement("div", { className: "dsh-mem-rb-muted", style: { marginTop: 8 } }, lastNote)
832
1156
  : null,
833
1157
  rbError
834
- ? react.createElement("div", { style: { marginTop: 8, fontSize: 12, color: "#cf222e" } }, rbError)
1158
+ ? react.createElement("div", { style: { marginTop: 8, fontSize: 12, color: "var(--dsh-mem-danger)" } }, rbError)
835
1159
  : null,
836
1160
  confirmOpen
837
1161
  ? react.createElement(
838
- "div", {
839
- className: "dsh-mem-rb-overlay",
840
- onClick: function (e) {
841
- if (e.target === e.currentTarget) setConfirmOpen(false);
842
- },
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
+ ],
843
1179
  },
844
1180
  react.createElement(
845
- "div", { className: "dsh-mem-rb-modal" },
846
- react.createElement(
847
- "div", { style: { fontSize: 15, fontWeight: 600, marginBottom: 10 } },
848
- "确认重建全部记忆?",
849
- ),
850
- react.createElement(
851
- "div", null,
852
- "将以 L0 原始对话为事实源重新蒸馏:",
853
- react.createElement("b", null, rb.sessionCount + " 个会话 · " + rb.messageCount + " 条消息"),
854
- ",预计 ≥" + rb.estCalls + " 次蒸馏调用。",
855
- ),
856
- react.createElement(
857
- "div", { style: { marginTop: 8 } },
858
- "现有 L1 记忆 / L2 场景 / L3 画像会整体归档(*.bak.时间戳,可手工找回),随后清空重建;重建期间可正常对话,新对话的蒸馏优先进行;中途可取消,已重建部分保留。",
859
- ),
860
- react.createElement(
861
- "div", { style: { display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 16 } },
862
- react.createElement(
863
- "button", {
864
- type: "button",
865
- className: "dsh-mem-rb-btn",
866
- onClick: function () { setConfirmOpen(false); },
867
- },
868
- "取消",
869
- ),
870
- react.createElement(
871
- "button", {
872
- type: "button",
873
- className: "dsh-mem-rb-btn dsh-mem-rb-primary",
874
- disabled: busy,
875
- onClick: start,
876
- },
877
- busy ? "启动中…" : "开始重建",
878
- ),
879
- ),
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.时间戳,可手工找回),随后清空重建;重建期间可正常对话,新对话的蒸馏优先进行;中途可取消,已重建部分保留。",
880
1189
  ),
881
1190
  )
882
1191
  : null,
883
1192
  );
884
1193
  }
885
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
+
886
1466
  // ── Tab:概览(开关面板 + 计数) ──
887
1467
  function OverviewTab(props) {
888
1468
  var rpc = props.rpc;
@@ -939,22 +1519,27 @@ window.__ModuleLoader__.load({
939
1519
  });
940
1520
  };
941
1521
 
942
- var rows = [];
1522
+ // 统计分两段:计数瓦片(一眼可读)+ 明细行(目录/版本/时间线/队列)
1523
+ var tiles = [];
1524
+ var infos = [];
943
1525
  if (stats) {
944
- rows.push(["数据目录", stats.dataDir]);
945
- rows.push(["插件版本", "v" + stats.version]);
946
- rows.push(["默认档", modeLabel(stats.family)]);
947
- rows.push(["L0 今日消息", String(stats.l0Today)]);
948
- rows.push(["L1 原子记忆", stats.l1Count + " 条(累计抽取 " + stats.l1TotalExtracted + ")"]);
949
- rows.push(["L2 场景块", String(stats.sceneCount)]);
950
- rows.push(["L3 画像", stats.personaChars > 0 ? stats.personaChars + " 字符" : "未生成"]);
951
- rows.push(["上次 L1 抽取", fmtTime(stats.lastExtractAt)]);
952
- rows.push(["上次 L2 整合", fmtTime(stats.lastL2At)]);
953
- rows.push(["上次 L3 蒸馏", fmtTime(stats.lastL3At)]);
954
- rows.push([" L2 新记忆", stats.memoriesSinceL2 + " / 5"]);
955
- rows.push([" L3 新记忆", stats.memoriesSinceL3 + " / 20"]);
956
- 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)]);
957
1541
  }
1542
+ var degraded = stats && stats.message && stats.message !== "running";
958
1543
 
959
1544
  var master = settingsData && settingsData.settings ? settingsData.settings.enabled : true;
960
1545
  var ceilingNote = "";
@@ -1030,24 +1615,41 @@ window.__ModuleLoader__.load({
1030
1615
  : null,
1031
1616
  )
1032
1617
  : null,
1618
+ react.createElement(EmbeddingSection, { rpc: rpc }),
1033
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,
1034
1627
  error
1035
1628
  ? react.createElement("div", { style: S.error }, "获取状态失败:" + error)
1036
1629
  : !stats
1037
1630
  ? react.createElement("p", { style: S.intro }, "正在读取记忆状态…")
1038
1631
  : react.createElement(
1039
- "table",
1040
- { style: S.table },
1632
+ "div", null,
1633
+ react.createElement("div", { style: S.panelLabel }, "记忆概况"),
1041
1634
  react.createElement(
1042
- "tbody", null,
1043
- rows.map(function (row) {
1635
+ "div", { style: S.statGrid },
1636
+ tiles.map(function (t) {
1044
1637
  return react.createElement(
1045
- "tr", { key: row[0] },
1046
- react.createElement("td", { style: S.tdKey }, row[0]),
1047
- 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),
1048
1642
  );
1049
1643
  }),
1050
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
+ }),
1051
1653
  ),
1052
1654
  react.createElement(
1053
1655
  "p", { style: S.hint },
@@ -1076,6 +1678,9 @@ window.__ModuleLoader__.load({
1076
1678
  var loadingState = react.useState(false);
1077
1679
  var loading = loadingState[0];
1078
1680
  var setLoading = loadingState[1];
1681
+ var truncState = react.useState(false);
1682
+ var truncated = truncState[0];
1683
+ var setTruncated = truncState[1];
1079
1684
  var errState = react.useState(null);
1080
1685
  var error = errState[0];
1081
1686
  var setError = errState[1];
@@ -1098,15 +1703,20 @@ window.__ModuleLoader__.load({
1098
1703
  var last = lastState[0];
1099
1704
  var setLast = lastState[1];
1100
1705
 
1706
+ // 请求序列号:快速连续搜索/翻页时丢弃过期响应(慢的旧响应不得覆盖新结果)
1707
+ var seqRef = react.useRef(0);
1708
+
1101
1709
  var fetchPage = react.useCallback(function (conds, offset, append) {
1102
1710
  setLoading(true);
1103
1711
  setError(null);
1712
+ var token = ++seqRef.current;
1104
1713
  var payload = { limit: limit, offset: offset };
1105
1714
  if (conds.query) payload.query = conds.query;
1106
1715
  if (conds.type) payload.type = conds.type;
1107
1716
  if (conds.scene) payload.scene = conds.scene;
1108
1717
  rpc("dsh-memory/list-records", payload)
1109
1718
  .then(function (r) {
1719
+ if (token !== seqRef.current) return;
1110
1720
  setLoading(false);
1111
1721
  if (!r || !r.ok) {
1112
1722
  setError(r && r.error ? r.error.message : "RPC error");
@@ -1116,9 +1726,11 @@ window.__ModuleLoader__.load({
1116
1726
  setItems(function (prev) { return append ? prev.concat(v.items) : v.items; });
1117
1727
  setHasMore(!!v.hasMore);
1118
1728
  setTotal(v.total === undefined || v.total === null ? null : v.total);
1729
+ setTruncated(!!v.truncated);
1119
1730
  if (v.scenes) setSceneOptions(v.scenes);
1120
1731
  })
1121
1732
  .catch(function (e) {
1733
+ if (token !== seqRef.current) return;
1122
1734
  setLoading(false);
1123
1735
  setError(String((e && e.message) || e));
1124
1736
  });
@@ -1146,8 +1758,8 @@ window.__ModuleLoader__.load({
1146
1758
  "div", null,
1147
1759
  react.createElement(
1148
1760
  "div", { style: S.toolbar },
1149
- react.createElement("input", {
1150
- style: S.input,
1761
+ react.createElement(NInput, {
1762
+ style: { flex: 1, minWidth: 160 },
1151
1763
  placeholder: "搜索记忆内容(BM25 关键词)…",
1152
1764
  value: query,
1153
1765
  onChange: function (e) { setQuery(e.target.value); },
@@ -1156,7 +1768,7 @@ window.__ModuleLoader__.load({
1156
1768
  react.createElement(
1157
1769
  "select",
1158
1770
  {
1159
- style: S.select,
1771
+ className: "dsh-mem-select",
1160
1772
  value: typeFilter,
1161
1773
  onChange: function (e) { setTypeFilter(e.target.value); },
1162
1774
  },
@@ -1168,7 +1780,7 @@ window.__ModuleLoader__.load({
1168
1780
  react.createElement(
1169
1781
  "select",
1170
1782
  {
1171
- style: S.select,
1783
+ className: "dsh-mem-select",
1172
1784
  value: sceneFilter,
1173
1785
  onChange: function (e) { setSceneFilter(e.target.value); },
1174
1786
  },
@@ -1177,18 +1789,24 @@ window.__ModuleLoader__.load({
1177
1789
  return react.createElement("option", { key: s, value: s }, s.length > 24 ? s.slice(0, 24) + "…" : s);
1178
1790
  }),
1179
1791
  ),
1180
- react.createElement("button", { style: S.button, onClick: search }, "搜索"),
1792
+ react.createElement(NButton, { onClick: search }, "搜索"),
1181
1793
  ),
1182
1794
  react.createElement(
1183
1795
  "div", { style: Object.assign({}, S.flexRow, { marginBottom: 10 }) },
1184
1796
  react.createElement("span", { style: S.muted }, loading ? "加载中…" : countText),
1185
1797
  react.createElement("div", { style: S.grow }),
1186
- react.createElement("button", {
1187
- style: S.button,
1798
+ react.createElement(NButton, {
1188
1799
  onClick: function () { fetchPage(last, 0, false); },
1189
1800
  }, "刷新"),
1190
1801
  ),
1191
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,
1192
1810
  items.length === 0 && !loading && !error
1193
1811
  ? react.createElement("p", { style: S.intro }, "暂无记忆。对话几轮后,蒸馏管线会自动抽取记忆。")
1194
1812
  : items.map(function (m) {
@@ -1197,18 +1815,15 @@ window.__ModuleLoader__.load({
1197
1815
  "div",
1198
1816
  {
1199
1817
  key: m.id,
1200
- style: S.card,
1818
+ className: "dsh-mem-card dsh-mem-card-hover",
1819
+ style: Object.assign({}, S.card, { cursor: "pointer" }),
1201
1820
  onClick: function () { setExpandedId(open ? null : m.id); },
1202
1821
  },
1203
1822
  react.createElement(
1204
1823
  "div", { style: S.cardHead },
1205
1824
  react.createElement(
1206
1825
  "span",
1207
- {
1208
- style: Object.assign({}, S.tag, {
1209
- background: TYPE_COLORS[m.type] || "#666",
1210
- }),
1211
- },
1826
+ { className: "dsh-mem-tag dsh-mem-tag-" + m.type },
1212
1827
  TYPE_LABELS[m.type] || m.type,
1213
1828
  ),
1214
1829
  react.createElement("span", { style: S.muted }, "优先级 " + m.priority),
@@ -1223,13 +1838,13 @@ window.__ModuleLoader__.load({
1223
1838
  ? react.createElement(
1224
1839
  "div", { style: S.detail },
1225
1840
  "id: " + m.id + "\n" +
1226
- "情境: " + (m.scene || "") + "\n" +
1841
+ "情境: " + (m.scene || "-") + "\n" +
1227
1842
  "版本: v" + m.version + "(去重合并次数 " + m.version + ")\n" +
1228
1843
  "创建: " + fmtTime(m.createdAt) + "\n" +
1229
- "活跃时间: " + (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" +
1230
1845
  (m.sourceMessageIds && m.sourceMessageIds.length > 0
1231
1846
  ? "来源消息: " + m.sourceMessageIds.join(", ")
1232
- : "来源消息: "),
1847
+ : "来源消息: -"),
1233
1848
  )
1234
1849
  : null,
1235
1850
  );
@@ -1238,9 +1853,11 @@ window.__ModuleLoader__.load({
1238
1853
  ? react.createElement(
1239
1854
  "div", { style: S.flexRow },
1240
1855
  react.createElement("div", { style: S.grow }),
1241
- react.createElement("button", {
1242
- style: S.button,
1243
- 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
+ },
1244
1861
  }, loading ? "加载中…" : "加载更多"),
1245
1862
  )
1246
1863
  : null,
@@ -1274,7 +1891,7 @@ window.__ModuleLoader__.load({
1274
1891
  "div", { style: Object.assign({}, S.flexRow, { marginBottom: 10 }) },
1275
1892
  react.createElement("span", { style: S.muted }, items ? items.length + " 个场景块" : "加载中…"),
1276
1893
  react.createElement("div", { style: S.grow }),
1277
- react.createElement("button", { style: S.button, onClick: load }, "刷新"),
1894
+ react.createElement(NButton, { onClick: load }, "刷新"),
1278
1895
  ),
1279
1896
  error ? react.createElement("div", { style: S.error }, error) : null,
1280
1897
  items && items.length === 0
@@ -1282,7 +1899,7 @@ window.__ModuleLoader__.load({
1282
1899
  : null,
1283
1900
  (items || []).map(function (s) {
1284
1901
  return react.createElement(
1285
- "div", { key: s.path, style: S.card },
1902
+ "div", { key: s.path, className: "dsh-mem-card", style: S.card },
1286
1903
  react.createElement(
1287
1904
  "div", { style: S.sceneHead },
1288
1905
  react.createElement("span", { style: S.sceneTitle }, s.path),
@@ -1309,11 +1926,18 @@ window.__ModuleLoader__.load({
1309
1926
  var contentState = react.useState(null);
1310
1927
  var content = contentState[0];
1311
1928
  var setContent = contentState[1];
1929
+ var errState = react.useState(null);
1930
+ var error = errState[0];
1931
+ var setError = errState[1];
1312
1932
 
1313
1933
  var load = react.useCallback(function () {
1934
+ setError(null);
1314
1935
  rpc("dsh-memory/persona", {})
1315
- .then(function (r) { if (r && r.ok) setContent(r.value.content); })
1316
- .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)); });
1317
1941
  }, [rpc]);
1318
1942
 
1319
1943
  react.useEffect(function () { load(); }, [load]);
@@ -1323,10 +1947,17 @@ window.__ModuleLoader__.load({
1323
1947
  react.createElement(
1324
1948
  "div", { style: Object.assign({}, S.flexRow, { marginBottom: 10 }) },
1325
1949
  react.createElement("span", { style: S.muted },
1326
- content === null ? "加载中…" : content ? content.length + " 字符" : "未生成画像"),
1950
+ error ? "加载失败" : content === null ? "加载中…" : content ? content.length + " 字符" : "未生成画像"),
1327
1951
  react.createElement("div", { style: S.grow }),
1328
- react.createElement("button", { style: S.button, onClick: load }, "刷新"),
1952
+ react.createElement(NButton, { onClick: load }, "刷新"),
1329
1953
  ),
1954
+ error
1955
+ ? react.createElement(
1956
+ "div",
1957
+ { style: Object.assign({}, S.error, { marginBottom: 10 }) },
1958
+ "画像读取失败:" + error + "(点右上“刷新”重试)",
1959
+ )
1960
+ : null,
1330
1961
  content
1331
1962
  ? react.createElement("pre", { style: S.pre }, content)
1332
1963
  : content === null
@@ -1341,11 +1972,18 @@ window.__ModuleLoader__.load({
1341
1972
  var linesState = react.useState(null);
1342
1973
  var lines = linesState[0];
1343
1974
  var setLines = linesState[1];
1975
+ var errState = react.useState(null);
1976
+ var error = errState[0];
1977
+ var setError = errState[1];
1344
1978
 
1345
1979
  var load = react.useCallback(function () {
1980
+ setError(null);
1346
1981
  rpc("dsh-memory/log-tail", { lines: 200 })
1347
- .then(function (r) { if (r && r.ok) setLines(r.value.lines); })
1348
- .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)); });
1349
1987
  }, [rpc]);
1350
1988
 
1351
1989
  react.useEffect(function () { load(); }, [load]);
@@ -1354,11 +1992,17 @@ window.__ModuleLoader__.load({
1354
1992
  "div", null,
1355
1993
  react.createElement(
1356
1994
  "div", { style: Object.assign({}, S.flexRow, { marginBottom: 10 }) },
1357
- 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)"),
1358
1996
  react.createElement("div", { style: S.grow }),
1359
- react.createElement("button", { style: S.button, onClick: load }, "刷新"),
1997
+ react.createElement(NButton, { onClick: load }, "刷新"),
1360
1998
  ),
1361
- 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") || "(暂无日志)"),
1362
2006
  );
1363
2007
  }
1364
2008
 
@@ -1371,12 +2015,66 @@ window.__ModuleLoader__.load({
1371
2015
  ["log", "日志"],
1372
2016
  ];
1373
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
+
1374
2069
  function MemoryPanel(props) {
1375
2070
  var rpc = props.rpc;
1376
2071
  var tabState = react.useState("overview");
1377
2072
  var tab = tabState[0];
1378
2073
  var setTab = tabState[1];
1379
2074
 
2075
+ ensureThemeStyle(); // 设置页挂载即注入主题令牌与组件样式
2076
+ react.useEffect(function () { watchSidebarIcon(); }, []);
2077
+
1380
2078
  var body;
1381
2079
  if (tab === "overview") body = react.createElement(OverviewTab, { rpc: rpc });
1382
2080
  else if (tab === "records") body = react.createElement(RecordsTab, { rpc: rpc });
@@ -1386,7 +2084,7 @@ window.__ModuleLoader__.load({
1386
2084
 
1387
2085
  return react.createElement(
1388
2086
  "div",
1389
- { style: S.section },
2087
+ { className: "dsh-mem-root", style: S.section },
1390
2088
  react.createElement("h2", { style: S.heading }, "记忆 (Memory)"),
1391
2089
  react.createElement(
1392
2090
  "p", { style: S.intro },
@@ -1399,7 +2097,7 @@ window.__ModuleLoader__.load({
1399
2097
  "button",
1400
2098
  {
1401
2099
  key: t[0],
1402
- 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",
1403
2101
  onClick: function () { setTab(t[0]); },
1404
2102
  },
1405
2103
  t[1],