dsh-layered-memory 0.5.4 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js CHANGED
@@ -160,6 +160,31 @@ window.__ModuleLoader__.load({
160
160
  padding: "4px 12px",
161
161
  marginBottom: 14,
162
162
  },
163
+ seg: {
164
+ display: "inline-flex",
165
+ border: "1px solid var(--dsw-alias-border-secondary, #e5e5e5)",
166
+ borderRadius: 8,
167
+ overflow: "hidden",
168
+ flexShrink: 0,
169
+ },
170
+ segBtn: {
171
+ padding: "4px 12px",
172
+ fontSize: 12,
173
+ lineHeight: "16px",
174
+ cursor: "pointer",
175
+ background: "transparent",
176
+ color: "inherit",
177
+ border: "none",
178
+ borderRight: "1px solid var(--dsw-alias-border-secondary, #e5e5e5)",
179
+ },
180
+ segBtnOn: {
181
+ background: "var(--dsw-alias-interactive-bg-primary, #1a7f37)",
182
+ color: "#fff",
183
+ fontWeight: 600,
184
+ },
185
+ segBtnOff: {
186
+ background: "var(--dsw-alias-bg-secondary, #f6f8fa)",
187
+ },
163
188
  flexRow: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" },
164
189
  grow: { flex: 1 },
165
190
  sceneHead: { display: "flex", alignItems: "baseline", gap: 10, marginBottom: 6, flexWrap: "wrap" },
@@ -228,6 +253,36 @@ window.__ModuleLoader__.load({
228
253
  );
229
254
  }
230
255
 
256
+ // ── Segmented 组件:分段选择器(记忆设置页的蒸馏思考档位等单选项) ──
257
+ function Segmented(props) {
258
+ var value = props.value;
259
+ var disabled = !!props.disabled;
260
+ return react.createElement(
261
+ "div",
262
+ { style: Object.assign({}, S.seg, disabled ? S.switchDisabled : null) },
263
+ props.options.map(function (opt, i) {
264
+ var on = opt.key === value;
265
+ return react.createElement(
266
+ "span",
267
+ {
268
+ key: opt.key,
269
+ style: Object.assign(
270
+ {},
271
+ S.segBtn,
272
+ on ? S.segBtnOn : S.segBtnOff,
273
+ i === props.options.length - 1 ? { borderRight: "none" } : null,
274
+ disabled ? { cursor: "not-allowed" } : null,
275
+ ),
276
+ onClick: function () {
277
+ if (!disabled && !on && props.onChange) props.onChange(opt.key);
278
+ },
279
+ },
280
+ opt.label,
281
+ );
282
+ }),
283
+ );
284
+ }
285
+
231
286
  // ── 会话记忆档位控件(输入栏 pill + macOS 风格滑动选择器) ──
232
287
  // 档位顺序即滑轨顺序:关闭 → chat → work → 自动(默认档"自动"居右)
233
288
  var MODES = [
@@ -257,40 +312,54 @@ window.__ModuleLoader__.load({
257
312
  return 3;
258
313
  }
259
314
 
260
- /** 滑动选择器浮层(参考 macOS 滑动器:拖拽圆头 + 松手吸附最近档;当前档由下方标签高亮指示)。 */
315
+ /** 滑动选择器浮层(参考 macOS 滑动器:拖拽圆头 1:1 连续跟手,松手按动量投影吸附最近档)。 */
261
316
  function ModeSlider(props) {
317
+ ensureFlowStyle(); // 玻璃材质 class 与流光共用同一张注入样式表
262
318
  var trackRef = react.useRef(null);
319
+ // 拖拽状态:{ x: 圆头连续位置 px, lastX: 上次指针 clientX, t: 时间戳, v: 速度 px/ms(EMA 平滑) }
263
320
  var dragState = react.useState(null);
264
- var dragIdx = dragState[0];
265
- var setDragIdx = dragState[1];
321
+ var drag = dragState[0];
322
+ var setDrag = dragState[1];
266
323
 
267
- var activeIdx = dragIdx === null ? modeIndex(props.mode) : dragIdx;
268
-
269
- var idxFromClientX = function (clientX) {
324
+ var clampX = function (x) {
325
+ if (x < 0) return 0;
326
+ if (x > INNER_W) return INNER_W;
327
+ return x;
328
+ };
329
+ var xFromClientX = function (clientX) {
270
330
  var rect = trackRef.current.getBoundingClientRect();
271
- var x = clientX - rect.left - THUMB / 2;
272
- if (x < 0) x = 0;
273
- if (x > INNER_W) x = INNER_W;
274
- return Math.round((x / INNER_W) * (MODES.length - 1));
331
+ return clampX(clientX - rect.left - THUMB / 2);
275
332
  };
276
333
 
277
334
  var onPointerDown = function (e) {
278
335
  e.preventDefault();
279
336
  e.currentTarget.setPointerCapture(e.pointerId);
280
- setDragIdx(idxFromClientX(e.clientX));
337
+ setDrag({ x: xFromClientX(e.clientX), lastX: e.clientX, t: e.timeStamp, v: 0 });
281
338
  };
282
339
  var onPointerMove = function (e) {
283
- if (dragIdx === null) return;
284
- setDragIdx(idxFromClientX(e.clientX));
340
+ if (drag === null) return;
341
+ var dt = e.timeStamp - drag.t;
342
+ var instV = dt > 0 ? (e.clientX - drag.lastX) / dt : drag.v;
343
+ setDrag({
344
+ x: xFromClientX(e.clientX),
345
+ lastX: e.clientX,
346
+ t: e.timeStamp,
347
+ v: drag.v * 0.7 + instV * 0.3, // EMA:瞬时抖动不放大,松手投影用
348
+ });
285
349
  };
286
350
  var onPointerUp = function (e) {
287
- if (dragIdx === null) return;
288
- var idx = idxFromClientX(e.clientX);
289
- setDragIdx(null);
351
+ if (drag === null) return;
352
+ // 动量投影(Designing Fluid Interfaces):按松手速度前瞻落点就近吸附;
353
+ // 投影量 clamp 到半档(±30px)——甩动最多把边界推到相邻档,绝不会跳两档
354
+ var projected = xFromClientX(e.clientX) + Math.max(-30, Math.min(30, drag.v * 120));
355
+ var idx = Math.round((clampX(projected) / INNER_W) * (MODES.length - 1));
356
+ setDrag(null);
290
357
  props.onCommit(MODES[idx].key);
291
358
  };
292
359
 
293
- var thumbLeft = (activeIdx / (MODES.length - 1)) * INNER_W;
360
+ // 拖拽中圆头 1:1 跟指针(连续位置,不吸附);静止时停在档位中心
361
+ var thumbLeft = drag !== null ? drag.x : (modeIndex(props.mode) / (MODES.length - 1)) * INNER_W;
362
+ var activeIdx = Math.min(MODES.length - 1, Math.max(0, Math.round((thumbLeft / INNER_W) * (MODES.length - 1))));
294
363
  var info = MODES[activeIdx];
295
364
 
296
365
  var stops = [];
@@ -310,7 +379,8 @@ window.__ModuleLoader__.load({
310
379
  width: 6,
311
380
  height: 6,
312
381
  borderRadius: "50%",
313
- background: active ? MODES[i].color : "#c6cbd1",
382
+ background: active ? MODES[i].color : "rgba(128,140,150,0.55)",
383
+ zIndex: 2,
314
384
  },
315
385
  },
316
386
  ),
@@ -331,9 +401,10 @@ window.__ModuleLoader__.load({
331
401
  cursor: "pointer",
332
402
  whiteSpace: "nowrap",
333
403
  fontWeight: active ? 600 : 400,
334
- color: active ? MODES[i].color : "var(--dsw-alias-label-tertiary, #888)",
404
+ // 未激活标签走主题 caption 令牌(浅色 #400 灰蓝 / 暗色 #600),玻璃面上保持可读
405
+ color: active ? MODES[i].color : "var(--dsw-alias-label-caption, #888)",
335
406
  },
336
- onClick: function () { if (dragIdx === null) props.onCommit(MODES[i].key); },
407
+ onClick: function () { if (drag === null) props.onCommit(MODES[i].key); },
337
408
  },
338
409
  MODES[i].label,
339
410
  ),
@@ -344,6 +415,8 @@ window.__ModuleLoader__.load({
344
415
  return react.createElement(
345
416
  "div",
346
417
  {
418
+ // 外壳只负责定位(带 transform 居中);玻璃材质拆到内层独立元素——
419
+ // Chromium 中 transform 元素上的 backdrop-filter 采样异常,玻璃会失效
347
420
  style: {
348
421
  position: "absolute",
349
422
  bottom: "calc(100% + 8px)",
@@ -351,67 +424,170 @@ window.__ModuleLoader__.load({
351
424
  left: "50%",
352
425
  transform: "translateX(-50%)",
353
426
  zIndex: 1000,
354
- background: "var(--dsw-alias-bg-primary, #fff)",
355
- border: "1px solid var(--dsw-alias-border-secondary, #e0e0e0)",
356
- borderRadius: 10,
357
- boxShadow: "0 8px 24px rgba(0,0,0,0.16)",
358
- padding: "12px 16px 30px",
359
427
  },
360
428
  },
429
+ react.createElement("div", {
430
+ className: "dsh-mem-glass",
431
+ style: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, pointerEvents: "none" },
432
+ }),
361
433
  react.createElement(
362
434
  "div",
363
- {
364
- ref: trackRef,
365
- style: {
366
- position: "relative",
367
- width: TRACK_W + THUMB,
368
- height: 16,
369
- touchAction: "none",
370
- cursor: dragIdx === null ? "pointer" : "grabbing",
435
+ { style: { position: "relative", padding: "12px 16px 30px" } },
436
+ react.createElement(
437
+ "div",
438
+ {
439
+ ref: trackRef,
440
+ style: {
441
+ position: "relative",
442
+ // 容器宽 = thumb 活动范围(0..INNER_W + THUMB),点击映射与视觉两端严格对齐
443
+ width: TRACK_W,
444
+ height: 16,
445
+ touchAction: "none",
446
+ cursor: drag === null ? "pointer" : "grabbing",
371
447
  },
372
448
  onPointerDown: onPointerDown,
373
449
  onPointerMove: onPointerMove,
374
450
  onPointerUp: onPointerUp,
375
451
  onPointerCancel: onPointerUp,
376
452
  },
377
- // 圆球先画、线条后画(叠在球面上)——线条从圆球中心穿过;
378
- // 线两端延伸到两端球的边缘(对称穿过首尾停点的中心);
379
- // 当前档由下方标签高亮指示(上方不放文字)
453
+ // 线条(底层):只连首尾停点的中心,两端不再露出停点外;
454
+ // thumb(当前档定位球)压最上层,不再被线条与停点穿过。
455
+ // 中性半透明灰在玻璃材质上深浅主题都可辨
380
456
  react.createElement("div", {
381
457
  style: {
382
458
  position: "absolute",
383
- left: thumbLeft,
384
- top: 1,
385
- width: THUMB,
386
- height: THUMB,
387
- borderRadius: "50%",
388
- background: "#fff",
389
- border: "1px solid " + info.color,
390
- boxShadow: "0 1px 4px rgba(0,0,0,0.3)",
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)",
391
465
  pointerEvents: "none",
392
- transition: dragIdx === null ? "left 120ms ease" : "none",
466
+ zIndex: 1,
393
467
  },
394
468
  }),
469
+ stops,
395
470
  react.createElement("div", {
396
471
  style: {
397
472
  position: "absolute",
398
- left: 0,
399
- width: TRACK_W,
400
- top: 7,
401
- height: 4,
402
- borderRadius: 999,
403
- background: "var(--dsw-alias-border-secondary, #e1e4e8)",
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)",
404
482
  pointerEvents: "none",
483
+ transition: drag === null ? "left 120ms ease" : "none",
484
+ zIndex: 3,
405
485
  },
406
486
  }),
407
- stops,
487
+ ),
488
+ props.error
489
+ ? react.createElement("div", { style: { fontSize: 11, color: "#cf222e", marginTop: 20, whiteSpace: "nowrap" } }, props.error)
490
+ : null,
408
491
  ),
409
- props.error
410
- ? react.createElement("div", { style: { fontSize: 11, color: "#cf222e", marginTop: 20, whiteSpace: "nowrap" } }, props.error)
411
- : null,
412
492
  );
413
493
  }
414
494
 
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;
501
+ var el = document.createElement("style");
502
+ el.id = FLOW_STYLE_ID;
503
+ el.textContent = [
504
+ // conic 角度动画需要 @property 注册才能插值;不支持的浏览器优雅降级为静态渐变边框
505
+ "@property --dsh-mem-angle { syntax: '<angle>'; initial-value: 0deg; inherits: false; }",
506
+ "@keyframes dshMemFlow { to { --dsh-mem-angle: 360deg; } }",
507
+ // 边缘流光(双层背景):border 区画旋转 conic 冷蓝光带;内部必须是【不透明】底色盖住
508
+ // 光带(半透明内层会让 conic 透进按钮内部,文字被光斑干扰——实测事故)。
509
+ // 不透明底 = 主题底混 12% 冷蓝(color-mix 出来 alpha=1),静态、随主题
510
+ ".dsh-mem-flow {",
511
+ " border: 1px solid transparent;",
512
+ " background:",
513
+ " 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)",
516
+ " ) padding-box,",
517
+ " 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;",
520
+ " animation: dshMemFlow 3s linear infinite;",
521
+ " color: #0284c7;",
522
+ "}",
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 {",
529
+ " 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);",
539
+ "}",
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);",
548
+ "}",
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; }",
551
+ "}",
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;",
561
+ "}",
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;",
567
+ "}",
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; }",
578
+ ".dsh-mem-rb-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.35);",
579
+ " display: flex; align-items: center; justify-content: center; z-index: 2000; }",
580
+ ".dsh-mem-rb-modal { width: 440px; max-width: calc(100vw - 48px); border-radius: 12px;",
581
+ " 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);",
584
+ " box-shadow: 0 16px 48px rgba(0,0,0,0.24); }",
585
+ "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); }",
587
+ ].join("\n");
588
+ document.head.appendChild(el);
589
+ }
590
+
415
591
  /** 输入栏 pill:点击展开滑动选择器;props 来自 conversation.input.left 的 zone 注入。 */
416
592
  function MemoryModePill(props) {
417
593
  var rpc = props.rpc;
@@ -474,6 +650,31 @@ window.__ModuleLoader__.load({
474
650
  if (!sessionId || !rpc) return null;
475
651
  var info = modeInfo(mode);
476
652
  var loaded = mode !== null;
653
+ var isAuto = loaded && mode === "auto";
654
+
655
+ if (isAuto) ensureFlowStyle();
656
+
657
+ // auto 档的边框/背景/文字色均由 .dsh-mem-flow 提供(双层背景画流光边 + 分主题文字),
658
+ // inline 不能设置这些——inline 优先级会盖掉 class 里的流光
659
+ var pillStyle = {
660
+ display: "inline-flex",
661
+ alignItems: "center",
662
+ gap: 4,
663
+ height: 24,
664
+ padding: "0 10px",
665
+ borderRadius: 999,
666
+ fontSize: 12,
667
+ fontWeight: 500,
668
+ lineHeight: "20px",
669
+ cursor: "pointer",
670
+ };
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)";
677
+ }
477
678
 
478
679
  return react.createElement(
479
680
  "div",
@@ -484,21 +685,8 @@ window.__ModuleLoader__.load({
484
685
  type: "button",
485
686
  title: "本会话记忆档位(点击切换)",
486
687
  onClick: function () { setOpen(!open); },
487
- style: {
488
- display: "inline-flex",
489
- alignItems: "center",
490
- gap: 4,
491
- height: 24,
492
- padding: "0 10px",
493
- borderRadius: 999,
494
- fontSize: 12,
495
- fontWeight: 500,
496
- lineHeight: "20px",
497
- cursor: "pointer",
498
- border: "1px solid " + (loaded ? info.color + "66" : "var(--dsw-alias-border-secondary, #ccc)"),
499
- background: loaded ? info.color + "14" : "var(--dsw-alias-bg-secondary, #f6f8fa)",
500
- color: loaded ? info.color : "var(--dsh-alias-label-tertiary, #888)",
501
- },
688
+ className: isAuto ? "dsh-mem-flow" : null,
689
+ style: pillStyle,
502
690
  },
503
691
  "记忆·",
504
692
  react.createElement("span", null, loaded ? info.label : "…"),
@@ -513,6 +701,188 @@ window.__ModuleLoader__.load({
513
701
  );
514
702
  }
515
703
 
704
+ // ── 重建面板:从 L0 重导 L1/L2/L3(确认弹窗 + 进度 + 取消;Light/Dark 双主题走 class) ──
705
+ var RB_PHASE_LABEL = {
706
+ preparing: "准备中(归档旧数据 · 清空检索库)",
707
+ distilling: "分块蒸馏中",
708
+ finalizing: "收尾(强制 L2 场景 + L3 画像)",
709
+ };
710
+
711
+ function RebuildPanel(props) {
712
+ var rpc = props.rpc;
713
+ var rbState = react.useState(null);
714
+ var rb = rbState[0];
715
+ var setRb = rbState[1];
716
+ var confirmState = react.useState(false);
717
+ var confirmOpen = confirmState[0];
718
+ var setConfirmOpen = confirmState[1];
719
+ var busyState = react.useState(false);
720
+ var busy = busyState[0];
721
+ var setBusy = busyState[1];
722
+ var errState = react.useState(null);
723
+ var rbError = errState[0];
724
+ var setRbError = errState[1];
725
+
726
+ var refresh = react.useCallback(function () {
727
+ rpc("dsh-memory/rebuild-status", {})
728
+ .then(function (r) {
729
+ if (r && r.ok) setRb(r.value);
730
+ })
731
+ .catch(function () {});
732
+ }, [rpc]);
733
+
734
+ react.useEffect(function () { refresh(); }, [refresh]);
735
+
736
+ // 运行中 1.5s 高频轮询进度;空闲不轮询(重新打开 Tab 时挂载刷新一次)
737
+ var running = !!(rb && rb.running);
738
+ react.useEffect(function () {
739
+ if (!running) return;
740
+ var timer = setInterval(refresh, 1500);
741
+ return function () { clearInterval(timer); };
742
+ }, [running, refresh]);
743
+
744
+ if (!rb || rb.supported === false) return null;
745
+ ensureFlowStyle();
746
+
747
+ var start = function () {
748
+ setBusy(true);
749
+ setRbError(null);
750
+ rpc("dsh-memory/rebuild-start", {})
751
+ .then(function (r) {
752
+ setBusy(false);
753
+ if (r && r.ok) {
754
+ setConfirmOpen(false);
755
+ setRb(r.value);
756
+ } else {
757
+ setRbError(r && r.error ? r.error.message : "启动失败");
758
+ }
759
+ })
760
+ .catch(function (e) {
761
+ setBusy(false);
762
+ setRbError(String((e && e.message) || e));
763
+ });
764
+ };
765
+ var cancel = function () {
766
+ setBusy(true);
767
+ rpc("dsh-memory/rebuild-cancel", {})
768
+ .then(function (r) {
769
+ setBusy(false);
770
+ if (r && r.ok) setRb(r.value);
771
+ })
772
+ .catch(function () {
773
+ setBusy(false);
774
+ });
775
+ };
776
+
777
+ var empty = !running && (rb.messageCount === 0 || rb.estCalls === 0);
778
+ var pct = rb.total > 0 ? Math.round((rb.done / rb.total) * 100) : 0;
779
+
780
+ var lastNote = null;
781
+ if (!running && rb.phase === "done") {
782
+ lastNote =
783
+ "上次重建:完成(" + rb.done + "/" + rb.total + " 会话,产出 " + rb.recordsBuilt + " 条记录)" +
784
+ (rb.finishedAt ? " · " + fmtTime(new Date(rb.finishedAt).toISOString()) : "");
785
+ } else if (!running && rb.phase === "cancelled") {
786
+ lastNote = "上次重建:已取消(完成 " + rb.done + "/" + rb.total + " 会话,已重建部分保留)";
787
+ } else if (!running && rb.phase === "failed") {
788
+ lastNote = "上次重建:失败 — " + (rb.error || "未知错误");
789
+ }
790
+
791
+ return react.createElement(
792
+ "div", { className: "dsh-mem-rb-card" },
793
+ react.createElement(
794
+ "div", { style: { display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" } },
795
+ react.createElement("div", { style: { fontWeight: 600, whiteSpace: "nowrap" } }, "重建记忆"),
796
+ react.createElement(
797
+ "div", { className: "dsh-mem-rb-muted", style: { flex: 1, minWidth: 180 } },
798
+ running
799
+ ? (RB_PHASE_LABEL[rb.phase] || rb.phase) + " · " + rb.done + "/" + rb.total + " 会话(" + pct + "%)"
800
+ : "从 L0 原始对话重新蒸馏 L1/L2/L3;旧数据先归档(不删除)",
801
+ ),
802
+ running
803
+ ? react.createElement("button", {
804
+ type: "button",
805
+ className: "dsh-mem-rb-btn",
806
+ disabled: busy || rb.cancelRequested,
807
+ onClick: cancel,
808
+ }, rb.cancelRequested ? "取消中…" : "取消重建")
809
+ : react.createElement("button", {
810
+ type: "button",
811
+ className: "dsh-mem-rb-btn dsh-mem-rb-danger",
812
+ disabled: busy || empty,
813
+ title: empty ? "L0 无消息,无可重建内容" : "重新蒸馏全部记忆",
814
+ onClick: function () { setConfirmOpen(true); },
815
+ }, busy ? "…" : "开始重建"),
816
+ ),
817
+ running
818
+ ? react.createElement(
819
+ "div", { style: { display: "flex", alignItems: "center", gap: 10, marginTop: 10 } },
820
+ react.createElement(
821
+ "div", { className: "dsh-mem-rb-bar" },
822
+ react.createElement("div", { className: "dsh-mem-rb-fill", style: { width: pct + "%" } }),
823
+ ),
824
+ react.createElement(
825
+ "span", { className: "dsh-mem-rb-muted", style: { whiteSpace: "nowrap" } },
826
+ "产出 " + rb.recordsBuilt + " 条",
827
+ ),
828
+ )
829
+ : null,
830
+ lastNote
831
+ ? react.createElement("div", { className: "dsh-mem-rb-muted", style: { marginTop: 8 } }, lastNote)
832
+ : null,
833
+ rbError
834
+ ? react.createElement("div", { style: { marginTop: 8, fontSize: 12, color: "#cf222e" } }, rbError)
835
+ : null,
836
+ confirmOpen
837
+ ? react.createElement(
838
+ "div", {
839
+ className: "dsh-mem-rb-overlay",
840
+ onClick: function (e) {
841
+ if (e.target === e.currentTarget) setConfirmOpen(false);
842
+ },
843
+ },
844
+ 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
+ ),
880
+ ),
881
+ )
882
+ : null,
883
+ );
884
+ }
885
+
516
886
  // ── Tab:概览(开关面板 + 计数) ──
517
887
  function OverviewTab(props) {
518
888
  var rpc = props.rpc;
@@ -630,11 +1000,37 @@ window.__ModuleLoader__.load({
630
1000
  disabled: !master,
631
1001
  onChange: function (v) { toggle("recall", v); },
632
1002
  }),
1003
+ settingsData.effort
1004
+ ? react.createElement(
1005
+ "div",
1006
+ { style: S.switchRow },
1007
+ react.createElement(Segmented, {
1008
+ value: settingsData.effort.current,
1009
+ options: [
1010
+ { key: "", label: "跟随配置" },
1011
+ { key: "off", label: "off" },
1012
+ { key: "high", label: "high" },
1013
+ { key: "max", label: "max" },
1014
+ ],
1015
+ disabled: !master,
1016
+ onChange: function (v) { toggle("reasoningEffort", v); },
1017
+ }),
1018
+ react.createElement("div", null,
1019
+ react.createElement("div", { style: S.switchLabel }, "蒸馏思考"),
1020
+ react.createElement(
1021
+ "div",
1022
+ { style: S.switchDesc },
1023
+ "当前生效 " + (settingsData.effort.effective || "(不传,跟随模型默认)") +
1024
+ (settingsData.effort.current ? "" : "(来自部署配置 llm.reasoningEffort)"),
1025
+ )),
1026
+ )
1027
+ : null,
633
1028
  ceilingNote
634
1029
  ? react.createElement("p", { style: S.hint }, ceilingNote)
635
1030
  : null,
636
1031
  )
637
1032
  : null,
1033
+ react.createElement(RebuildPanel, { rpc: rpc }),
638
1034
  error
639
1035
  ? react.createElement("div", { style: S.error }, "获取状态失败:" + error)
640
1036
  : !stats