dsh-layered-memory 0.8.2 → 0.8.4
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/README.en.md +25 -18
- package/README.md +30 -17
- package/assets/readme/bench-dialog.svg +38 -49
- package/assets/readme/bench-workflow.svg +29 -29
- package/dist/client.js +387 -120
- package/dist/config.d.ts +11 -4
- package/dist/config.js +11 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -2
- package/dist/llm.d.ts +28 -2
- package/dist/llm.js +92 -8
- package/dist/pipeline/runner.js +6 -2
- package/dist/settings.d.ts +3 -2
- package/dist/settings.js +3 -3
- package/dist/stats.js +51 -15
- package/dist/store/download-queue.d.ts +3 -0
- package/dist/store/download-queue.js +22 -2
- package/dist/store/embedding-source.d.ts +1 -1
- package/dist/store/embedding-source.js +3 -3
- package/dist/store/io.d.ts +10 -2
- package/dist/store/io.js +28 -6
- package/dist/store/l0.js +7 -2
- package/dist/store/l1.js +17 -7
- package/dist/store/local-embedding.d.ts +3 -1
- package/dist/store/local-embedding.js +5 -2
- package/dist/store/runtime-installer.d.ts +8 -1
- package/dist/store/runtime-installer.js +34 -5
- package/dist/store/scenes.d.ts +2 -1
- package/dist/store/scenes.js +10 -1
- package/dist/store/sqlite.js +3 -3
- package/dist/tools/index.js +12 -2
- package/package.json +17 -17
package/dist/client.js
CHANGED
|
@@ -770,15 +770,56 @@ window.__ModuleLoader__.load({
|
|
|
770
770
|
".dsh-mem-btn:focus-visible { outline: 2px solid var(--dsh-mem-accent); outline-offset: 1px; }",
|
|
771
771
|
".dsh-mem-btn:disabled { opacity: 0.45; cursor: not-allowed; }",
|
|
772
772
|
// ── 控件:输入框 / 下拉 ──
|
|
773
|
-
".dsh-mem-input
|
|
773
|
+
".dsh-mem-input {",
|
|
774
774
|
" padding: 5px 10px; font-size: 13px; border-radius: 8px; color: var(--dsh-mem-text-1);",
|
|
775
775
|
" border: 1px solid var(--dsh-mem-border); background: var(--dsh-mem-bg-card);",
|
|
776
776
|
" transition: border-color .15s ease, box-shadow .15s ease;",
|
|
777
777
|
"}",
|
|
778
|
-
".dsh-mem-input:focus
|
|
778
|
+
".dsh-mem-input:focus {",
|
|
779
779
|
" outline: none; border-color: var(--dsh-mem-accent);",
|
|
780
780
|
" box-shadow: 0 0 0 3px var(--dsh-mem-accent-weak);",
|
|
781
781
|
"}",
|
|
782
|
+
// ── 下拉(NSel 触发钮):观感同输入框(8px 圆角/同令牌边框底色),文字
|
|
783
|
+
// ellipsis + CSS 描边 chevron(展开旋转 180°);弹出面板见 .dsh-mem-pop ──
|
|
784
|
+
".dsh-mem-select {",
|
|
785
|
+
" display: inline-flex; align-items: center; justify-content: space-between; gap: 8px;",
|
|
786
|
+
" width: 100%; min-width: 0; padding: 5px 10px; font: inherit; font-size: 13px; line-height: 20px;",
|
|
787
|
+
" text-align: left; border-radius: 8px; cursor: pointer; color: var(--dsh-mem-text-1);",
|
|
788
|
+
" border: 1px solid var(--dsh-mem-border); background: var(--dsh-mem-bg-card);",
|
|
789
|
+
" transition: border-color .15s ease, box-shadow .15s ease;",
|
|
790
|
+
"}",
|
|
791
|
+
".dsh-mem-select:focus-visible {",
|
|
792
|
+
" outline: none; border-color: var(--dsh-mem-accent);",
|
|
793
|
+
" box-shadow: 0 0 0 3px var(--dsh-mem-accent-weak);",
|
|
794
|
+
"}",
|
|
795
|
+
".dsh-mem-select:disabled { opacity: 0.45; cursor: not-allowed; }",
|
|
796
|
+
".dsh-mem-select-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }",
|
|
797
|
+
// 下拉弹出面板:dsh MenuDropdown 同款——浮层 12 圆角 + menu 底 + lv3 投影,
|
|
798
|
+
// 选项行 10 圆角 + hover 底色 + 选中打勾(选项 active 由 data-active 标记)
|
|
799
|
+
".dsh-mem-sel { position: relative; display: inline-flex; min-width: 0; vertical-align: top; }",
|
|
800
|
+
".dsh-mem-sel-chev {",
|
|
801
|
+
" width: 8px; height: 8px; flex: none; margin-right: 2px;",
|
|
802
|
+
" border-right: 1.5px solid var(--dsh-mem-text-3); border-bottom: 1.5px solid var(--dsh-mem-text-3);",
|
|
803
|
+
" transform: rotate(45deg); transition: transform .12s ease;",
|
|
804
|
+
"}",
|
|
805
|
+
".dsh-mem-sel-chev-open { transform: rotate(225deg); }",
|
|
806
|
+
".dsh-mem-pop {",
|
|
807
|
+
" position: absolute; top: calc(100% + 6px); left: 0; z-index: 30;",
|
|
808
|
+
" min-width: 100%; width: max-content; max-width: 340px; max-height: 264px;",
|
|
809
|
+
" overflow-y: auto; overscroll-behavior: contain; padding: 4px;",
|
|
810
|
+
" background: var(--dsh-mem-bg-pop); border: 1px solid var(--dsh-mem-border-pop);",
|
|
811
|
+
" border-radius: 12px; box-shadow: var(--dsh-mem-shadow-pop);",
|
|
812
|
+
"}",
|
|
813
|
+
".dsh-mem-pop-opt {",
|
|
814
|
+
" display: flex; align-items: center; gap: 8px; width: 100%; box-sizing: border-box;",
|
|
815
|
+
" min-height: 32px; padding: 5px 10px; font: inherit; font-size: 13px; line-height: 20px;",
|
|
816
|
+
" text-align: left; color: var(--dsh-mem-text-1); cursor: pointer;",
|
|
817
|
+
" background: none; border: none; outline: none; border-radius: 10px;",
|
|
818
|
+
"}",
|
|
819
|
+
".dsh-mem-pop-opt:hover, .dsh-mem-pop-opt[data-active=\"1\"] { background: var(--dsh-mem-bg-hover); }",
|
|
820
|
+
".dsh-mem-pop-opt-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }",
|
|
821
|
+
".dsh-mem-pop-check { flex: none; font-size: 12px; color: var(--dsh-mem-text-1); }",
|
|
822
|
+
".dsh-mem-pop-empty { padding: 10px; font-size: 13px; color: var(--dsh-mem-text-3); }",
|
|
782
823
|
// ── Tab(下划线式):active 品牌蓝下划线 + 主文字色 ──
|
|
783
824
|
".dsh-mem-tab {",
|
|
784
825
|
" padding: 6px 12px; font-size: 13px; cursor: pointer; background: none; border: none;",
|
|
@@ -793,6 +834,8 @@ window.__ModuleLoader__.load({
|
|
|
793
834
|
" box-shadow: var(--dsh-mem-shadow-card);",
|
|
794
835
|
"}",
|
|
795
836
|
".dsh-mem-card-hover:hover { border-color: var(--dsh-mem-border-strong); }",
|
|
837
|
+
// ── 场景卡折叠箭头:展开态旋转 90°(过渡只做反馈,进 reduced-motion 压制名单) ──
|
|
838
|
+
".dsh-mem-scene-chev { display: inline-block; transition: transform .15s ease; color: var(--dsh-mem-text-3); }",
|
|
796
839
|
// ── 记忆类型标签:tint 风格(彩底淡色 + 彩字),--dsh-mem-tag-c 由类型类给定 ──
|
|
797
840
|
".dsh-mem-tag {",
|
|
798
841
|
" display: inline-block; padding: 1px 8px; border-radius: 999px;",
|
|
@@ -889,7 +932,7 @@ window.__ModuleLoader__.load({
|
|
|
889
932
|
".dsh-mem-rb-muted { font-size: 12px; color: var(--dsh-mem-text-3); }",
|
|
890
933
|
// reduced-motion 兜底放样式表末尾:同特异性下后置声明才能压过上面的组件类
|
|
891
934
|
"@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; }",
|
|
935
|
+
" .dsh-mem-root, .dsh-mem-root *, .dsh-mem-btn, .dsh-mem-input, .dsh-mem-select, .dsh-mem-rb-fill, .dsh-mem-scene-chev, .dsh-mem-sel-chev { transition: none; }",
|
|
893
936
|
" .dsh-mem-flow { animation: none; }",
|
|
894
937
|
"}",
|
|
895
938
|
].join("\n");
|
|
@@ -1463,6 +1506,158 @@ window.__ModuleLoader__.load({
|
|
|
1463
1506
|
);
|
|
1464
1507
|
}
|
|
1465
1508
|
|
|
1509
|
+
// ── 自绘下拉(NSel):对齐 dsh MenuDropdown(输入栏模型选择器同款)观感。
|
|
1510
|
+
// 原生 <select> 的弹出列表是操作系统绘的(方角、系统高亮色),appearance:none
|
|
1511
|
+
// 只能改闭合态外壳——所以整件换成按钮触发 + 浮层面板:面板 12px 圆角 /
|
|
1512
|
+
// bg-pop / border-pop / shadow-pop,选项行 10px 圆角 + hover 底色 + 选中打勾。
|
|
1513
|
+
// 键盘 ↑↓ 移动(wrap)、回车/空格开面板与选定、Esc/外点/焦点离开收起;
|
|
1514
|
+
// 焦点始终留在触发钮上(keydown 从钮冒泡到包装层统一处理)。 ──
|
|
1515
|
+
function NSel(props) {
|
|
1516
|
+
var options = props.options || [];
|
|
1517
|
+
var value = props.value || "";
|
|
1518
|
+
var disabled = !!props.disabled;
|
|
1519
|
+
var openState = react.useState(false);
|
|
1520
|
+
var open = openState[0];
|
|
1521
|
+
var setOpen = openState[1];
|
|
1522
|
+
var idxState = react.useState(-1);
|
|
1523
|
+
var idx = idxState[0];
|
|
1524
|
+
var setIdx = idxState[1];
|
|
1525
|
+
var wrapRef = react.useRef(null);
|
|
1526
|
+
var listRef = react.useRef(null);
|
|
1527
|
+
|
|
1528
|
+
var selectedLabel = "";
|
|
1529
|
+
for (var si = 0; si < options.length; si++) {
|
|
1530
|
+
if (options[si].id === value) selectedLabel = options[si].label;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
react.useEffect(function () {
|
|
1534
|
+
if (!open) return undefined;
|
|
1535
|
+
var onDown = function (e) {
|
|
1536
|
+
if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
|
|
1537
|
+
};
|
|
1538
|
+
document.addEventListener("mousedown", onDown);
|
|
1539
|
+
return function () { document.removeEventListener("mousedown", onDown); };
|
|
1540
|
+
}, [open]);
|
|
1541
|
+
|
|
1542
|
+
// 键盘移动后把活动项滚进可视区(鼠标 hover 同步活动项索引)
|
|
1543
|
+
react.useEffect(function () {
|
|
1544
|
+
if (!open || !listRef.current) return;
|
|
1545
|
+
var el = listRef.current.querySelector('[data-active="1"]');
|
|
1546
|
+
if (el && el.scrollIntoView) el.scrollIntoView({ block: "nearest" });
|
|
1547
|
+
}, [open, idx]);
|
|
1548
|
+
|
|
1549
|
+
var indexOfValue = function () {
|
|
1550
|
+
for (var i = 0; i < options.length; i++) if (options[i].id === value) return i;
|
|
1551
|
+
return -1;
|
|
1552
|
+
};
|
|
1553
|
+
var closeMenu = function (refocus) {
|
|
1554
|
+
setOpen(false);
|
|
1555
|
+
setIdx(-1);
|
|
1556
|
+
if (refocus && wrapRef.current) {
|
|
1557
|
+
var btn = wrapRef.current.querySelector("button");
|
|
1558
|
+
if (btn && btn.focus) btn.focus();
|
|
1559
|
+
}
|
|
1560
|
+
};
|
|
1561
|
+
var pick = function (id) {
|
|
1562
|
+
closeMenu(true);
|
|
1563
|
+
if (id !== value && props.onChange) props.onChange(id);
|
|
1564
|
+
};
|
|
1565
|
+
var moveActive = function (delta) {
|
|
1566
|
+
var n = options.length;
|
|
1567
|
+
if (n === 0) return;
|
|
1568
|
+
var cur = idx >= 0 ? idx : indexOfValue();
|
|
1569
|
+
if (cur < 0) cur = delta > 0 ? -1 : 0;
|
|
1570
|
+
setIdx(delta > 0 ? (cur + 1) % n : (cur - 1 + n) % n);
|
|
1571
|
+
};
|
|
1572
|
+
var onKey = function (e) {
|
|
1573
|
+
if (disabled) return;
|
|
1574
|
+
if (e.key === "Escape") {
|
|
1575
|
+
if (open) { e.preventDefault(); closeMenu(true); }
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
1579
|
+
e.preventDefault();
|
|
1580
|
+
if (!open) { setOpen(true); setIdx(indexOfValue()); }
|
|
1581
|
+
else moveActive(e.key === "ArrowDown" ? 1 : -1);
|
|
1582
|
+
return;
|
|
1583
|
+
}
|
|
1584
|
+
if (!open && (e.key === "Enter" || e.key === " ")) {
|
|
1585
|
+
e.preventDefault();
|
|
1586
|
+
setOpen(true);
|
|
1587
|
+
setIdx(indexOfValue());
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
if (open && e.key === "Enter") {
|
|
1591
|
+
e.preventDefault();
|
|
1592
|
+
var t = idx >= 0 ? idx : indexOfValue();
|
|
1593
|
+
if (t < 0) t = 0;
|
|
1594
|
+
if (options[t]) pick(options[t].id);
|
|
1595
|
+
}
|
|
1596
|
+
};
|
|
1597
|
+
var onBlur = function (e) {
|
|
1598
|
+
if (!open) return;
|
|
1599
|
+
var to = e.relatedTarget;
|
|
1600
|
+
if (!to || (wrapRef.current && !wrapRef.current.contains(to))) setOpen(false);
|
|
1601
|
+
};
|
|
1602
|
+
|
|
1603
|
+
return react.createElement(
|
|
1604
|
+
"div",
|
|
1605
|
+
{ className: "dsh-mem-sel", style: props.style, ref: wrapRef, onKeyDown: onKey, onBlur: onBlur },
|
|
1606
|
+
react.createElement(
|
|
1607
|
+
"button",
|
|
1608
|
+
{
|
|
1609
|
+
type: "button",
|
|
1610
|
+
className: "dsh-mem-select" + (open ? " dsh-mem-select-open" : ""),
|
|
1611
|
+
disabled: disabled,
|
|
1612
|
+
"aria-haspopup": "listbox",
|
|
1613
|
+
"aria-expanded": open,
|
|
1614
|
+
onClick: function () {
|
|
1615
|
+
if (open) closeMenu(false);
|
|
1616
|
+
else { setOpen(true); setIdx(-1); }
|
|
1617
|
+
},
|
|
1618
|
+
},
|
|
1619
|
+
react.createElement("span", { className: "dsh-mem-select-label" }, selectedLabel || props.placeholder || "(请选择)"),
|
|
1620
|
+
react.createElement("span", {
|
|
1621
|
+
className: "dsh-mem-sel-chev" + (open ? " dsh-mem-sel-chev-open" : ""),
|
|
1622
|
+
"aria-hidden": true,
|
|
1623
|
+
}),
|
|
1624
|
+
),
|
|
1625
|
+
open && !disabled
|
|
1626
|
+
? react.createElement(
|
|
1627
|
+
"div", { className: "dsh-mem-pop", ref: listRef, role: "listbox" },
|
|
1628
|
+
options.length === 0
|
|
1629
|
+
? react.createElement("div", { className: "dsh-mem-pop-empty" }, "无选项")
|
|
1630
|
+
: options.map(function (o, i) {
|
|
1631
|
+
return react.createElement(
|
|
1632
|
+
"button",
|
|
1633
|
+
{
|
|
1634
|
+
type: "button",
|
|
1635
|
+
key: o.id,
|
|
1636
|
+
className: "dsh-mem-pop-opt" + (o.id === value ? " dsh-mem-pop-opt-on" : ""),
|
|
1637
|
+
role: "option",
|
|
1638
|
+
"aria-selected": o.id === value,
|
|
1639
|
+
"data-active": i === idx ? "1" : "0",
|
|
1640
|
+
// 阻断 mousedown 默认行为(抢焦点):焦点留在触发钮上,避免
|
|
1641
|
+
// blur-关面板把后续 click 吞掉(点选项无反应的事故根因)
|
|
1642
|
+
onMouseDown: function (e) { if (e.preventDefault) e.preventDefault(); },
|
|
1643
|
+
onClick: function () { pick(o.id); },
|
|
1644
|
+
onMouseEnter: function () { if (idx !== i) setIdx(i); },
|
|
1645
|
+
},
|
|
1646
|
+
react.createElement("span", { className: "dsh-mem-pop-opt-label" }, o.label),
|
|
1647
|
+
o.id === value
|
|
1648
|
+
? react.createElement("span", { className: "dsh-mem-pop-check" }, "✓")
|
|
1649
|
+
: null,
|
|
1650
|
+
);
|
|
1651
|
+
}),
|
|
1652
|
+
)
|
|
1653
|
+
: null,
|
|
1654
|
+
);
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
// 供应商 → 模型列表缓存(模块级,会话内存活):切换供应商时缓存命中即时渲染
|
|
1658
|
+
// (后台仍刷新),面板首次加载时预取全部供应商——消除"切换后模型按钮几秒真空期"
|
|
1659
|
+
var modelsCache = {};
|
|
1660
|
+
|
|
1466
1661
|
// ── 蒸馏模型选择器(供应商/模型两级下拉)──
|
|
1467
1662
|
// 数据源:dsh-memory/llm-providers(供应商目录 + 当前覆盖 + 实际生效路由)与
|
|
1468
1663
|
// dsh-memory/llm-models(所选供应商的模型列表)。写入走 settings-set 的
|
|
@@ -1483,14 +1678,21 @@ window.__ModuleLoader__.load({
|
|
|
1483
1678
|
var manualModel = manualState[0];
|
|
1484
1679
|
var setManualModel = manualState[1];
|
|
1485
1680
|
|
|
1681
|
+
// 写入在途时丢弃轮询响应(在途请求读到的是写入前的旧值,直接 set 会把
|
|
1682
|
+
// 乐观更新闪回旧文本);写入成功后主动拉一次服务器真值收敛
|
|
1683
|
+
var pendingWrites = react.useRef(0);
|
|
1684
|
+
|
|
1685
|
+
function refreshInfo() {
|
|
1686
|
+
rpc("dsh-memory/llm-providers", {})
|
|
1687
|
+
.then(function (r) {
|
|
1688
|
+
if (r && r.ok && pendingWrites.current === 0) setInfo(r.value);
|
|
1689
|
+
})
|
|
1690
|
+
.catch(function () {});
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1486
1693
|
react.useEffect(function () {
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
.then(function (r) { if (r && r.ok) setInfo(r.value); })
|
|
1490
|
-
.catch(function () {});
|
|
1491
|
-
};
|
|
1492
|
-
load();
|
|
1493
|
-
var timer = setInterval(load, 5000);
|
|
1694
|
+
refreshInfo();
|
|
1695
|
+
var timer = setInterval(refreshInfo, 5000);
|
|
1494
1696
|
return function () { clearInterval(timer); };
|
|
1495
1697
|
}, [rpc]);
|
|
1496
1698
|
|
|
@@ -1500,32 +1702,80 @@ window.__ModuleLoader__.load({
|
|
|
1500
1702
|
var curProvider = draft || (info && info.current ? info.current.provider : "");
|
|
1501
1703
|
var curModel = info && info.current ? info.current.model : "";
|
|
1502
1704
|
|
|
1705
|
+
// 预取各供应商模型列表进缓存(后台、失败忽略):llm-providers 轮询每次都会
|
|
1706
|
+
// 走一遍,缓存齐了以后是空循环
|
|
1707
|
+
react.useEffect(function () {
|
|
1708
|
+
if (!info || !info.providers) return;
|
|
1709
|
+
info.providers.forEach(function (p) {
|
|
1710
|
+
if (!p.id || modelsCache[p.id]) return;
|
|
1711
|
+
rpc("dsh-memory/llm-models", { provider: p.id })
|
|
1712
|
+
.then(function (r) { if (r && r.ok && r.value) modelsCache[p.id] = r.value.models || []; })
|
|
1713
|
+
.catch(function () {});
|
|
1714
|
+
});
|
|
1715
|
+
}, [rpc, info]);
|
|
1716
|
+
|
|
1503
1717
|
react.useEffect(function () {
|
|
1504
1718
|
if (!curProvider) { setModels(null); return undefined; }
|
|
1505
1719
|
var alive = true;
|
|
1506
|
-
|
|
1720
|
+
var cached = modelsCache[curProvider];
|
|
1721
|
+
var picked = false;
|
|
1722
|
+
// 缓存命中:同步先渲染(跳过"加载中"真空期),后台仍刷新一遍
|
|
1723
|
+
if (cached) {
|
|
1724
|
+
setModels(cached);
|
|
1725
|
+
// 供应商切换自动落第一个模型(缓存路径):当前覆盖不属于该供应商 → 成对写覆盖
|
|
1726
|
+
if (draft && cached.length > 0 && !cached.some(function (m) { return m.id === curModel; })) {
|
|
1727
|
+
picked = true;
|
|
1728
|
+
writeLlm({ distillProvider: draft, distillModel: cached[0].id });
|
|
1729
|
+
}
|
|
1730
|
+
} else {
|
|
1731
|
+
setModels(null);
|
|
1732
|
+
}
|
|
1507
1733
|
rpc("dsh-memory/llm-models", { provider: curProvider })
|
|
1508
|
-
.then(function (r) {
|
|
1509
|
-
|
|
1734
|
+
.then(function (r) {
|
|
1735
|
+
if (!alive) return;
|
|
1736
|
+
var list = (r && r.ok && r.value && r.value.models) || [];
|
|
1737
|
+
modelsCache[curProvider] = list;
|
|
1738
|
+
setModels(list);
|
|
1739
|
+
// 自动落第一个模型(网络路径,缓存未命中或缓存路径未写时)
|
|
1740
|
+
if (!picked && draft && list.length > 0 && !list.some(function (m) { return m.id === curModel; })) {
|
|
1741
|
+
writeLlm({ distillProvider: draft, distillModel: list[0].id });
|
|
1742
|
+
}
|
|
1743
|
+
})
|
|
1744
|
+
.catch(function () { if (alive && !cached) setModels([]); });
|
|
1510
1745
|
return function () { alive = false; };
|
|
1511
1746
|
}, [rpc, curProvider]);
|
|
1512
1747
|
|
|
1513
1748
|
var writeLlm = function (patch) {
|
|
1514
1749
|
if (!info) return;
|
|
1515
|
-
//
|
|
1750
|
+
// 乐观更新:覆盖必须写成视图键 current.provider/model(按钮文本读这对键)——
|
|
1751
|
+
// 直接合并 settings 键 distillProvider/distillModel 对显示层是空操作,文本要
|
|
1752
|
+
// 等 5s 轮询才变;effective 同步推导("当前生效"描述行),失败回滚
|
|
1516
1753
|
var prev = info;
|
|
1754
|
+
var prevCurrent = prev.current || {};
|
|
1755
|
+
var np = patch.distillProvider !== undefined ? patch.distillProvider : prevCurrent.provider;
|
|
1756
|
+
var nm = patch.distillModel !== undefined ? patch.distillModel : prevCurrent.model;
|
|
1757
|
+
var nextEffective;
|
|
1758
|
+
if (np && nm) nextEffective = { provider: np, model: nm };
|
|
1759
|
+
else if (prev.default) nextEffective = { provider: prev.default.provider, model: prev.default.model };
|
|
1760
|
+
else nextEffective = null;
|
|
1517
1761
|
setInfo(Object.assign({}, prev, {
|
|
1518
|
-
current: Object.assign({},
|
|
1762
|
+
current: Object.assign({}, prevCurrent, { provider: np, model: nm }),
|
|
1763
|
+
effective: nextEffective,
|
|
1519
1764
|
}));
|
|
1520
1765
|
setDraft("");
|
|
1766
|
+
pendingWrites.current += 1;
|
|
1521
1767
|
rpc("dsh-memory/settings-set", patch)
|
|
1522
1768
|
.then(function (r) {
|
|
1769
|
+
pendingWrites.current -= 1;
|
|
1523
1770
|
if (!r || !r.ok) {
|
|
1524
1771
|
setInfo(prev);
|
|
1525
1772
|
setDraft(draft);
|
|
1773
|
+
} else {
|
|
1774
|
+
refreshInfo();
|
|
1526
1775
|
}
|
|
1527
1776
|
})
|
|
1528
1777
|
.catch(function () {
|
|
1778
|
+
pendingWrites.current -= 1;
|
|
1529
1779
|
setInfo(prev);
|
|
1530
1780
|
setDraft(draft);
|
|
1531
1781
|
});
|
|
@@ -1540,7 +1790,8 @@ window.__ModuleLoader__.load({
|
|
|
1540
1790
|
react.createElement("div", { style: S.switchLabel }, "蒸馏模型"),
|
|
1541
1791
|
react.createElement(
|
|
1542
1792
|
"div", { style: S.switchDesc },
|
|
1543
|
-
"已由部署配置固定:" + info.effective.provider + " / " + info.effective.model
|
|
1793
|
+
"已由部署配置固定:" + info.effective.provider + " / " + info.effective.model +
|
|
1794
|
+
"(如需在页面切换,请移除 profile cordis.patch.yml 中 llm 的 provider/model 覆盖)",
|
|
1544
1795
|
)),
|
|
1545
1796
|
);
|
|
1546
1797
|
}
|
|
@@ -1563,35 +1814,29 @@ window.__ModuleLoader__.load({
|
|
|
1563
1814
|
providerOptions.push({ id: curProvider, label: curProvider + "(已不在列表)" });
|
|
1564
1815
|
}
|
|
1565
1816
|
|
|
1566
|
-
var modelOptions =
|
|
1567
|
-
|
|
1568
|
-
);
|
|
1817
|
+
var modelOptions = (models || []).map(function (m) {
|
|
1818
|
+
return { id: m.id, label: m.name !== m.id ? m.name + "(" + m.id + ")" : m.id };
|
|
1819
|
+
});
|
|
1569
1820
|
if (curModel && !modelKnown) {
|
|
1570
1821
|
modelOptions.push({ id: curModel, label: curModel + "(已不在列表)" });
|
|
1571
1822
|
}
|
|
1572
1823
|
|
|
1573
1824
|
return react.createElement(
|
|
1574
1825
|
"div", { style: Object.assign({}, S.switchRow, { flexWrap: "wrap" }) },
|
|
1575
|
-
react.createElement(
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
setDraft(v);
|
|
1588
|
-
}
|
|
1589
|
-
},
|
|
1826
|
+
react.createElement(NSel, {
|
|
1827
|
+
style: { flexShrink: 0, width: 220 },
|
|
1828
|
+
options: providerOptions,
|
|
1829
|
+
value: curProvider,
|
|
1830
|
+
disabled: disabled,
|
|
1831
|
+
placeholder: "跟随默认",
|
|
1832
|
+
onChange: function (v) {
|
|
1833
|
+
if (v === "") {
|
|
1834
|
+
writeLlm({ distillProvider: "", distillModel: "" });
|
|
1835
|
+
} else {
|
|
1836
|
+
setDraft(v);
|
|
1837
|
+
}
|
|
1590
1838
|
},
|
|
1591
|
-
|
|
1592
|
-
return react.createElement("option", { key: o.id, value: o.id }, o.label);
|
|
1593
|
-
}),
|
|
1594
|
-
),
|
|
1839
|
+
}),
|
|
1595
1840
|
!curProvider
|
|
1596
1841
|
? null
|
|
1597
1842
|
: manualInput
|
|
@@ -1607,21 +1852,17 @@ window.__ModuleLoader__.load({
|
|
|
1607
1852
|
}
|
|
1608
1853
|
},
|
|
1609
1854
|
})
|
|
1610
|
-
: react.createElement(
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
},
|
|
1855
|
+
: react.createElement(NSel, {
|
|
1856
|
+
style: { flex: 1, minWidth: 160 },
|
|
1857
|
+
options: modelOptions,
|
|
1858
|
+
// 拉取期(无缓存首切)显示占位而非上一供应商的过期模型名
|
|
1859
|
+
value: models === null ? "" : curModel,
|
|
1860
|
+
disabled: disabled || models === null,
|
|
1861
|
+
placeholder: models === null ? "加载模型列表…" : "(选择模型)",
|
|
1862
|
+
onChange: function (v) {
|
|
1863
|
+
writeLlm({ distillProvider: curProvider, distillModel: v });
|
|
1620
1864
|
},
|
|
1621
|
-
|
|
1622
|
-
return react.createElement("option", { key: o.id, value: o.id }, o.label);
|
|
1623
|
-
}),
|
|
1624
|
-
),
|
|
1865
|
+
}),
|
|
1625
1866
|
react.createElement("div", null,
|
|
1626
1867
|
react.createElement("div", { style: S.switchLabel }, "蒸馏模型"),
|
|
1627
1868
|
react.createElement(
|
|
@@ -1803,7 +2044,7 @@ window.__ModuleLoader__.load({
|
|
|
1803
2044
|
"div", { style: S.switchDesc },
|
|
1804
2045
|
"各蒸馏层单次输出的 token 上限(抽取/去重/L2/L3);留空或 0 = 跟随默认(当前生效 " +
|
|
1805
2046
|
layers.map(function (l) { return eff[l[0]] || "?"; }).join(" / ") +
|
|
1806
|
-
");思考档 high/max 时实际限额自动 ×4",
|
|
2047
|
+
");思考档 high/xhigh/max 时实际限额自动 ×4",
|
|
1807
2048
|
)),
|
|
1808
2049
|
),
|
|
1809
2050
|
react.createElement(
|
|
@@ -1866,7 +2107,7 @@ window.__ModuleLoader__.load({
|
|
|
1866
2107
|
if (key === "reasoningEffort" && next.effort) {
|
|
1867
2108
|
next.effort = Object.assign({}, prev.effort, {
|
|
1868
2109
|
current: value,
|
|
1869
|
-
effective: value
|
|
2110
|
+
effective: value,
|
|
1870
2111
|
});
|
|
1871
2112
|
}
|
|
1872
2113
|
setSettingsData(next);
|
|
@@ -1956,29 +2197,37 @@ window.__ModuleLoader__.load({
|
|
|
1956
2197
|
// 分组:蒸馏参数(模型路由 / 思考档位 / 输出预算)
|
|
1957
2198
|
react.createElement("div", { style: S.panelLabel }, "蒸馏参数"),
|
|
1958
2199
|
settingsData.effort
|
|
1959
|
-
?
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
react.createElement(
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
2200
|
+
? (function () {
|
|
2201
|
+
// 档位表来自当前生效模型的能力声明(settings-get 的 effort.options;
|
|
2202
|
+
// 空声明 → 只显示 high)。首项固定「自动」(key='',点击回写空串=
|
|
2203
|
+
// 按模型能力解析),选过显式档位后仍可回到自动;失效档位高亮实际生效值。
|
|
2204
|
+
var f = settingsData.effort;
|
|
2205
|
+
var opts = f.options && f.options.length ? f.options : ["high"];
|
|
2206
|
+
var segOptions = [{ key: "", label: "自动" }].concat(
|
|
2207
|
+
opts.map(function (k) { return { key: k, label: k }; })
|
|
2208
|
+
);
|
|
2209
|
+
var segValue = f.current === "" || opts.indexOf(f.current) >= 0
|
|
2210
|
+
? f.current
|
|
2211
|
+
: opts.indexOf(f.effective) >= 0 ? f.effective : "high";
|
|
2212
|
+
return react.createElement(
|
|
2213
|
+
"div",
|
|
2214
|
+
{ style: S.switchRow },
|
|
2215
|
+
react.createElement(Segmented, {
|
|
2216
|
+
value: segValue,
|
|
2217
|
+
options: segOptions,
|
|
2218
|
+
disabled: !master,
|
|
2219
|
+
onChange: function (v) { toggle("reasoningEffort", v); },
|
|
2220
|
+
}),
|
|
2221
|
+
react.createElement("div", null,
|
|
2222
|
+
react.createElement("div", { style: S.switchLabel }, "蒸馏思考"),
|
|
2223
|
+
react.createElement(
|
|
2224
|
+
"div",
|
|
2225
|
+
{ style: S.switchDesc },
|
|
2226
|
+
"当前生效 " + (f.effective || "(不传,跟随模型默认)") +
|
|
2227
|
+
(f.current ? "" : "(自动)"),
|
|
2228
|
+
)),
|
|
2229
|
+
);
|
|
2230
|
+
})()
|
|
1982
2231
|
: null,
|
|
1983
2232
|
react.createElement(LlmModelRow, { rpc: rpc, disabled: !master }),
|
|
1984
2233
|
react.createElement(BudgetInputs, {
|
|
@@ -2143,30 +2392,22 @@ window.__ModuleLoader__.load({
|
|
|
2143
2392
|
onChange: function (e) { setQuery(e.target.value); },
|
|
2144
2393
|
onKeyDown: function (e) { if (e.key === "Enter") search(); },
|
|
2145
2394
|
}),
|
|
2146
|
-
react.createElement(
|
|
2147
|
-
|
|
2148
|
-
{
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
})
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
value: sceneFilter,
|
|
2163
|
-
onChange: function (e) { setSceneFilter(e.target.value); },
|
|
2164
|
-
},
|
|
2165
|
-
react.createElement("option", { value: "" }, "全部情境"),
|
|
2166
|
-
sceneOptions.map(function (s) {
|
|
2167
|
-
return react.createElement("option", { key: s, value: s }, s.length > 24 ? s.slice(0, 24) + "…" : s);
|
|
2168
|
-
}),
|
|
2169
|
-
),
|
|
2395
|
+
react.createElement(NSel, {
|
|
2396
|
+
style: { maxWidth: 200 },
|
|
2397
|
+
options: [{ id: "", label: "全部类型" }].concat(typeChoices.map(function (t) {
|
|
2398
|
+
return { id: t, label: TYPE_LABELS[t] || t };
|
|
2399
|
+
})),
|
|
2400
|
+
value: typeFilter,
|
|
2401
|
+
onChange: setTypeFilter,
|
|
2402
|
+
}),
|
|
2403
|
+
react.createElement(NSel, {
|
|
2404
|
+
style: { maxWidth: 220 },
|
|
2405
|
+
options: [{ id: "", label: "全部情境" }].concat(sceneOptions.map(function (s) {
|
|
2406
|
+
return { id: s, label: s.length > 24 ? s.slice(0, 24) + "…" : s };
|
|
2407
|
+
})),
|
|
2408
|
+
value: sceneFilter,
|
|
2409
|
+
onChange: setSceneFilter,
|
|
2410
|
+
}),
|
|
2170
2411
|
react.createElement(NButton, { onClick: search }, "搜索"),
|
|
2171
2412
|
),
|
|
2172
2413
|
react.createElement(
|
|
@@ -2243,6 +2484,44 @@ window.__ModuleLoader__.load({
|
|
|
2243
2484
|
}
|
|
2244
2485
|
|
|
2245
2486
|
// ── Tab:L2 场景 ──
|
|
2487
|
+
// ── 场景卡:默认收起(头部 + 摘要行),点击头部展开/收起正文——与记忆卡的
|
|
2488
|
+
// 点击展开范式同款(cursor pointer + card-hover 描边),箭头 ▸ 展开态旋转 90°。
|
|
2489
|
+
function SceneCard(props) {
|
|
2490
|
+
var s = props.s;
|
|
2491
|
+
var openState = react.useState(false);
|
|
2492
|
+
var open = openState[0];
|
|
2493
|
+
var setOpen = openState[1];
|
|
2494
|
+
return react.createElement(
|
|
2495
|
+
"div", { className: "dsh-mem-card dsh-mem-card-hover", style: S.card },
|
|
2496
|
+
react.createElement(
|
|
2497
|
+
"div",
|
|
2498
|
+
{
|
|
2499
|
+
style: Object.assign({}, S.sceneHead, { cursor: "pointer", userSelect: "none" }),
|
|
2500
|
+
onClick: function () { setOpen(!open); },
|
|
2501
|
+
},
|
|
2502
|
+
react.createElement(
|
|
2503
|
+
"span",
|
|
2504
|
+
{ className: "dsh-mem-scene-chev", style: { transform: open ? "rotate(90deg)" : "none" } },
|
|
2505
|
+
"▸",
|
|
2506
|
+
),
|
|
2507
|
+
react.createElement("span", { style: S.sceneTitle }, s.path),
|
|
2508
|
+
s.heat ? react.createElement("span", { style: S.muted }, "热度 " + s.heat) : null,
|
|
2509
|
+
react.createElement("div", { style: S.grow }),
|
|
2510
|
+
react.createElement("span", { style: S.muted }, "更新 " + fmtTime(s.updated)),
|
|
2511
|
+
),
|
|
2512
|
+
s.summary
|
|
2513
|
+
? react.createElement(
|
|
2514
|
+
"div",
|
|
2515
|
+
{ style: Object.assign({}, S.muted, { marginBottom: 6 }) },
|
|
2516
|
+
s.summary,
|
|
2517
|
+
)
|
|
2518
|
+
: null,
|
|
2519
|
+
open
|
|
2520
|
+
? react.createElement("pre", { style: S.pre }, s.content || "(空)")
|
|
2521
|
+
: null,
|
|
2522
|
+
);
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2246
2525
|
function ScenesTab(props) {
|
|
2247
2526
|
var rpc = props.rpc;
|
|
2248
2527
|
var itemsState = react.useState(null);
|
|
@@ -2276,24 +2555,7 @@ window.__ModuleLoader__.load({
|
|
|
2276
2555
|
? react.createElement("p", { style: S.intro }, "暂无场景块。累计 5 条新记忆后 L2 会自动整合出第一个场景。")
|
|
2277
2556
|
: null,
|
|
2278
2557
|
(items || []).map(function (s) {
|
|
2279
|
-
return react.createElement(
|
|
2280
|
-
"div", { key: s.path, className: "dsh-mem-card", style: S.card },
|
|
2281
|
-
react.createElement(
|
|
2282
|
-
"div", { style: S.sceneHead },
|
|
2283
|
-
react.createElement("span", { style: S.sceneTitle }, s.path),
|
|
2284
|
-
s.heat ? react.createElement("span", { style: S.muted }, "热度 " + s.heat) : null,
|
|
2285
|
-
react.createElement("div", { style: S.grow }),
|
|
2286
|
-
react.createElement("span", { style: S.muted }, "更新 " + fmtTime(s.updated)),
|
|
2287
|
-
),
|
|
2288
|
-
s.summary
|
|
2289
|
-
? react.createElement(
|
|
2290
|
-
"div",
|
|
2291
|
-
{ style: Object.assign({}, S.muted, { marginBottom: 6 }) },
|
|
2292
|
-
s.summary,
|
|
2293
|
-
)
|
|
2294
|
-
: null,
|
|
2295
|
-
react.createElement("pre", { style: S.pre }, s.content || "(空)"),
|
|
2296
|
-
);
|
|
2558
|
+
return react.createElement(SceneCard, { key: s.path, s: s });
|
|
2297
2559
|
}),
|
|
2298
2560
|
);
|
|
2299
2561
|
}
|
|
@@ -2353,6 +2615,7 @@ window.__ModuleLoader__.load({
|
|
|
2353
2615
|
var errState = react.useState(null);
|
|
2354
2616
|
var error = errState[0];
|
|
2355
2617
|
var setError = errState[1];
|
|
2618
|
+
var preRef = react.useRef(null);
|
|
2356
2619
|
|
|
2357
2620
|
var load = react.useCallback(function () {
|
|
2358
2621
|
setError(null);
|
|
@@ -2365,6 +2628,10 @@ window.__ModuleLoader__.load({
|
|
|
2365
2628
|
}, [rpc]);
|
|
2366
2629
|
|
|
2367
2630
|
react.useEffect(function () { load(); }, [load]);
|
|
2631
|
+
// 默认滚到最底:看日志要看最新一条(tail 语义),加载/刷新后贴底
|
|
2632
|
+
react.useEffect(function () {
|
|
2633
|
+
if (lines && preRef.current) preRef.current.scrollTop = preRef.current.scrollHeight;
|
|
2634
|
+
}, [lines]);
|
|
2368
2635
|
|
|
2369
2636
|
return react.createElement(
|
|
2370
2637
|
"div", null,
|
|
@@ -2380,7 +2647,7 @@ window.__ModuleLoader__.load({
|
|
|
2380
2647
|
{ style: Object.assign({}, S.error, { marginBottom: 10 }) },
|
|
2381
2648
|
"日志读取失败:" + error + "(点右上“刷新”重试)",
|
|
2382
2649
|
)
|
|
2383
|
-
: react.createElement("pre", { style: S.pre }, (lines || []).join("\n") || "(暂无日志)"),
|
|
2650
|
+
: react.createElement("pre", { style: S.pre, ref: preRef }, (lines || []).join("\n") || "(暂无日志)"),
|
|
2384
2651
|
);
|
|
2385
2652
|
}
|
|
2386
2653
|
|