dsh-layered-memory 0.8.8 → 0.8.9

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
@@ -31,7 +31,7 @@ var __defProp = Object.defineProperty;
31
31
  module.exports = __toCommonJS(entry_exports);
32
32
 
33
33
  // client/src/panel.tsx
34
- var import_react13 = require("react");
34
+ var import_react14 = require("react");
35
35
 
36
36
  // client/src/sidebar-icon.ts
37
37
  var BOOK_ICON_SVG = '<svg data-mem-icon="1" viewBox="0 0 16 16" width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg" style="flex-shrink:0"><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" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/><path d="M8 3.4v9.3" stroke="currentColor" stroke-width="1.2"/></svg>';
@@ -524,7 +524,7 @@ var __defProp = Object.defineProperty;
524
524
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
525
525
  "span",
526
526
  {
527
- title: opt.disabledTitle || "",
527
+ title: opt.disabledTitle || opt.title || "",
528
528
  style: {
529
529
  ...S.segBtn,
530
530
  ...on ? S.segBtnOn : null,
@@ -918,7 +918,7 @@ var __defProp = Object.defineProperty;
918
918
  }
919
919
 
920
920
  // client/src/tabs/OverviewTab.tsx
921
- var import_react9 = require("react");
921
+ var import_react10 = require("react");
922
922
 
923
923
  // client/src/format.ts
924
924
  var TYPE_LABELS = {
@@ -998,6 +998,9 @@ var __defProp = Object.defineProperty;
998
998
  return 3;
999
999
  }
1000
1000
 
1001
+ // client/src/tabs/DistillSettings.tsx
1002
+ var import_react7 = require("react");
1003
+
1001
1004
  // client/src/tabs/BudgetInputs.tsx
1002
1005
  var import_react4 = require("react");
1003
1006
  var import_jsx_runtime4 = require("react/jsx-runtime");
@@ -1007,13 +1010,19 @@ var __defProp = Object.defineProperty;
1007
1010
  ["l2", "L2 场景"],
1008
1011
  ["l3", "L3 画像"]
1009
1012
  ];
1013
+ var SCOPE_KEYS = {
1014
+ l1: ["extract", "dedup"],
1015
+ l2: ["l2"],
1016
+ l3: ["l3"]
1017
+ };
1010
1018
  function BudgetInputs(props) {
1011
1019
  const rpc = props.rpc;
1012
1020
  const disabled = !!props.disabled;
1013
1021
  const data = props.data;
1014
1022
  const setData = props.setData;
1015
1023
  const onError = props.onError;
1016
- const layers = LAYERS;
1024
+ const scope = props.scope ?? "all";
1025
+ const layers = scope === "all" || scope === "input" ? LAYERS : LAYERS.filter((l) => SCOPE_KEYS[scope].includes(l[0]));
1017
1026
  const [draft, setDraft] = (0, import_react4.useState)(null);
1018
1027
  if (!data || !data.budgets) return null;
1019
1028
  const cur = data.budgets.current || {};
@@ -1137,518 +1146,174 @@ var __defProp = Object.defineProperty;
1137
1146
  key
1138
1147
  );
1139
1148
  };
1149
+ const showOutputs = scope !== "input";
1150
+ const showInput = scope === "all" || scope === "input";
1151
+ const effNote = layers.map((l) => eff[l[0]] || "?").join(" / ");
1152
+ const rowLabel = (key) => key === "extract" ? "抽取输出" : key === "dedup" ? "去重输出" : key === "l2" ? "L2 输出" : "L3 输出";
1153
+ const rowStyle = { display: "flex", alignItems: "center", gap: 8 };
1140
1154
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
1141
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: S.switchRow, children: [
1142
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { ...S.flexRow, gap: 6 }, children: layers.map((l) => {
1143
- return inputBox(
1155
+ showOutputs ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { marginTop: 12 }, children: [
1156
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1157
+ "div",
1158
+ {
1159
+ style: S.switchLabel,
1160
+ title: (scope === "l1" ? "L1 的抽取与去重是两次独立调用,输出限额各自设置(路由共用同一条链)。" : "") + "留空或 0 = 跟随默认(当前生效 " + effNote + ");思考档 high/xhigh/max 时实际限额自动 ×4",
1161
+ children: "输出预算"
1162
+ }
1163
+ ),
1164
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: 6, marginTop: 4 }, children: layers.map((l) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: rowStyle, children: [
1165
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { width: 68, flexShrink: 0, fontSize: 12, color: "var(--dsh-mem-text-2)" }, children: rowLabel(l[0]) }),
1166
+ inputBox(
1144
1167
  l[0],
1145
1168
  l[1],
1146
1169
  l[1] + " 输出预算(token,留空 = 默认 " + (def[l[0]] || "?") + ")",
1147
- 92,
1170
+ 110,
1148
1171
  def[l[0]],
1149
1172
  commitOutputs
1150
- );
1151
- }) }),
1152
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
1153
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: S.switchLabel, children: "输出预算" }),
1154
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: S.switchDesc, children: "各蒸馏层单次输出的 token 上限(抽取/去重/L2/L3);留空或 0 = 跟随默认(当前生效 " + layers.map((l) => eff[l[0]] || "?").join(" / ") + ");思考档 high/xhigh/max 时实际限额自动 ×4" })
1155
- ] })
1156
- ] }),
1157
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: S.switchRow, children: [
1158
- inputBox(
1159
- "input",
1160
- "输入",
1161
- "单次蒸馏输入字符上限(≈token,中文 1 字≈1 token;留空 = 跟随配置 " + (ib.fallback || "?") + ")",
1162
- 120,
1163
- ib.fallback,
1164
- commitInput
1173
+ ),
1174
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { fontSize: 11, color: "var(--dsh-mem-text-3)" }, children: "token" })
1175
+ ] }, l[0])) })
1176
+ ] }) : null,
1177
+ showInput ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { marginTop: 12 }, children: [
1178
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1179
+ "div",
1180
+ {
1181
+ style: S.switchLabel,
1182
+ title: "单次蒸馏调用的输入字符上限(≈token):L1 抽取按此分块、L2/L3 超限截断;留空或 0 = 跟随配置(当前生效 " + (effIn || "?") + ",来自 llm.maxInputChars)",
1183
+ children: "输入预算"
1184
+ }
1165
1185
  ),
1166
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
1167
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: S.switchLabel, children: "输入预算" }),
1168
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: S.switchDesc, children: "单次蒸馏调用的输入字符上限(≈token):L1 抽取按此分块、L2/L3 超限截断;留空或 0 = 跟随配置(当前生效 " + (effIn || "?") + ",来自 llm.maxInputChars)" })
1169
- ] })
1170
- ] })
1186
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: 6, marginTop: 4 }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: rowStyle, children: [
1187
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { width: 68, flexShrink: 0, fontSize: 12, color: "var(--dsh-mem-text-2)" }, children: "单次输入" }),
1188
+ inputBox(
1189
+ "input",
1190
+ "输入",
1191
+ "单次蒸馏输入字符上限(≈token,中文 1 字≈1 token;留空 = 跟随配置 " + (ib.fallback || "?") + ")",
1192
+ 110,
1193
+ ib.fallback,
1194
+ commitInput
1195
+ ),
1196
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { fontSize: 11, color: "var(--dsh-mem-text-3)" }, children: "字符" })
1197
+ ] }) })
1198
+ ] }) : null
1171
1199
  ] });
1172
1200
  }
1173
1201
 
1174
- // client/src/tabs/EmbeddingSection.tsx
1175
- var import_react5 = require("react");
1176
-
1177
- // client/src/rpc.ts
1178
- function makeRpc(ctx) {
1179
- return (endpoint, payload) => {
1180
- if (!ctx.connection || !ctx.connection.rpc) return Promise.reject(new Error("connection 服务不可用"));
1181
- return ctx.connection.rpc.call("/rpc", endpoint, payload ?? {});
1182
- };
1183
- }
1184
- function asLoose(rpc) {
1185
- return rpc;
1186
- }
1202
+ // client/src/tabs/RouteChainEditor.tsx
1203
+ var import_react6 = require("react");
1187
1204
 
1188
- // client/src/tabs/EmbeddingSection.tsx
1205
+ // client/src/ui/NSel.tsx
1206
+ var import_react5 = require("react");
1189
1207
  var import_jsx_runtime5 = require("react/jsx-runtime");
1190
- function EmbeddingSection(props) {
1191
- const rpc = props.rpc;
1192
- const loose = asLoose(rpc);
1193
- const [st, setSt] = (0, import_react5.useState)(null);
1194
- const [err, setErr] = (0, import_react5.useState)(null);
1195
- const busyPollRef = (0, import_react5.useRef)(null);
1196
- const load = (0, import_react5.useCallback)(() => {
1197
- rpc("dsh-memory/embedding-state-get", {}).then((r) => {
1198
- if (r && r.ok && r.value && r.value.supported !== false) {
1199
- const v = r.value;
1200
- if (busyPollRef.current) {
1201
- busyPollRef.current.v = !!(v.download && (v.download.phase === "downloading" || v.download.phase === "verifying") || v.apply.busy || v.reindex && v.reindex.running || v.runtime && v.runtime.phase === "installing");
1202
- }
1203
- setSt(v);
1204
- setErr(null);
1205
- } else if (r && r.ok && r.value && r.value.supported === false) {
1206
- setSt(null);
1207
- setErr("__unsupported__");
1208
- } else {
1209
- setErr(r && !r.ok ? r.error.message : "RPC error");
1210
- }
1211
- }).catch((e) => {
1212
- setErr(String(e && e.message || e));
1213
- });
1214
- }, [rpc]);
1215
- (0, import_react5.useEffect)(() => {
1216
- load();
1217
- }, [load]);
1208
+ function NSel(props) {
1209
+ const options = props.options || [];
1210
+ const value = props.value || "";
1211
+ const disabled = !!props.disabled;
1212
+ const [open, setOpen] = (0, import_react5.useState)(false);
1213
+ const [idx, setIdx] = (0, import_react5.useState)(-1);
1214
+ const wrapRef = (0, import_react5.useRef)(null);
1215
+ const listRef = (0, import_react5.useRef)(null);
1216
+ let selectedLabel = "";
1217
+ for (let si = 0; si < options.length; si++) {
1218
+ if (options[si].id === value) selectedLabel = options[si].label;
1219
+ }
1218
1220
  (0, import_react5.useEffect)(() => {
1219
- let stopped = false;
1220
- const busyFlag = { v: false };
1221
- busyPollRef.current = busyFlag;
1222
- const tick = () => {
1223
- if (stopped) return;
1224
- load();
1225
- timer = setTimeout(tick, busyFlag.v ? 1200 : 5e3);
1221
+ if (!open) return void 0;
1222
+ const onDown = (e) => {
1223
+ if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
1226
1224
  };
1227
- let timer = setTimeout(tick, 1200);
1225
+ document.addEventListener("mousedown", onDown);
1228
1226
  return () => {
1229
- stopped = true;
1230
- clearTimeout(timer);
1231
- busyPollRef.current = null;
1227
+ document.removeEventListener("mousedown", onDown);
1232
1228
  };
1233
- }, [load]);
1234
- const call = (endpoint, payload, confirmText) => {
1235
- if (confirmText && !window.confirm(confirmText)) return;
1236
- loose(endpoint, payload).then((r) => {
1237
- if (!r || !r.ok) setErr(r && r.error ? r.error.message : "操作失败");
1238
- else {
1239
- setErr(null);
1240
- load();
1241
- }
1242
- }).catch((e) => {
1243
- setErr(String(e && e.message || e));
1244
- });
1229
+ }, [open]);
1230
+ (0, import_react5.useEffect)(() => {
1231
+ if (!open || !listRef.current) return;
1232
+ const el = listRef.current.querySelector('[data-active="1"]');
1233
+ if (el && el.scrollIntoView) el.scrollIntoView({ block: "nearest" });
1234
+ }, [open, idx]);
1235
+ const indexOfValue = () => {
1236
+ for (let i = 0; i < options.length; i++) if (options[i].id === value) return i;
1237
+ return -1;
1245
1238
  };
1246
- if (err === "__unsupported__") {
1247
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dsh-mem-rb-card", children: [
1248
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { fontWeight: 600, marginBottom: 4 }, children: "语义检索(嵌入)" }),
1249
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-mem-rb-muted", children: "存储处于降级状态,嵌入管理不可用。" })
1250
- ] });
1251
- }
1252
- if (!st) {
1253
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dsh-mem-rb-card", children: [
1254
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-mem-rb-muted", children: err ? "嵌入状态读取失败:" + err : "嵌入状态读取中…" }),
1255
- err ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(NButton, { style: { marginTop: 8 }, onClick: load, children: "重试" }) : null
1256
- ] });
1257
- }
1258
- const switchConfirm = "切换嵌入源后将按新模型重建向量索引(期间语义检索暂退化为关键词匹配,不影响对话)。确定切换?";
1259
- const onSource = (key) => {
1260
- if (key === "local") {
1261
- const ready = [];
1262
- for (let i = 0; i < st.models.length; i++) if (st.models[i].state === "downloaded") ready.push(st.models[i].id);
1263
- if (ready.length === 0) {
1264
- setErr("请先在下方下载一个本地嵌入模型,再切换到本地档。");
1265
- return;
1239
+ const closeMenu = (refocus) => {
1240
+ setOpen(false);
1241
+ setIdx(-1);
1242
+ if (refocus && wrapRef.current) {
1243
+ const btn = wrapRef.current.querySelector("button");
1244
+ if (btn && btn.focus) btn.focus();
1245
+ }
1246
+ };
1247
+ const pick = (id) => {
1248
+ closeMenu(true);
1249
+ if (id !== value && props.onChange) props.onChange(id);
1250
+ };
1251
+ const moveActive = (delta) => {
1252
+ const n = options.length;
1253
+ if (n === 0) return;
1254
+ let cur = idx >= 0 ? idx : indexOfValue();
1255
+ if (cur < 0) cur = delta > 0 ? -1 : 0;
1256
+ setIdx(delta > 0 ? ((cur + 1) % n + n) % n : ((cur - 1) % n + n) % n);
1257
+ };
1258
+ const onKey = (e) => {
1259
+ if (disabled) return;
1260
+ if (e.key === "Escape") {
1261
+ if (open) {
1262
+ e.preventDefault();
1263
+ closeMenu(true);
1266
1264
  }
1267
- const current = st.activeModel && ready.indexOf(st.activeModel) >= 0 ? st.activeModel : ready[0];
1268
- call("dsh-memory/embedding-source-set", { source: "local", activeModel: current }, switchConfirm);
1269
1265
  return;
1270
1266
  }
1271
- call("dsh-memory/embedding-source-set", { source: key }, key === "remote" ? switchConfirm : null);
1272
- };
1273
- const dl = st.download;
1274
- const dlActive = dl && (dl.phase === "downloading" || dl.phase === "verifying");
1275
- const ap = st.apply;
1276
- const localInfo = st.local;
1277
- const rt = st.runtime;
1278
- let runtimeRow = null;
1279
- if (rt.phase === "installing") {
1280
- runtimeRow = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { marginTop: 8, fontSize: 12 }, children: [
1281
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: S.flexRow, children: [
1282
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "安装推理运行时中… 已耗时 " + Math.round(rt.elapsedMs / 1e3) + "s(约 100~200MB,视网络)" }),
1283
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: S.grow }),
1284
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(NButton, { onClick: () => call("dsh-memory/embedding-runtime-cancel", {}), children: "取消" })
1285
- ] }),
1286
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("pre", { style: { ...S.pre, maxHeight: 68, marginTop: 6, fontSize: 11, opacity: 0.85 }, children: (rt.lastLines || []).join("\n") || "等待 npm 输出…" })
1287
- ] });
1288
- } else if (rt.phase === "error") {
1289
- runtimeRow = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { marginTop: 8, fontSize: 12, color: "var(--dsh-mem-danger)" }, children: "运行时安装失败:" + (rt.error || "未知") + "(重新切换嵌入源可重试)" });
1290
- } else if (rt.phase === "ready") {
1291
- runtimeRow = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-mem-rb-muted", style: { marginTop: 8 }, children: "推理运行时就绪(transformers.js v" + rt.installedVersion + ")" });
1292
- } else if (st.source === "local") {
1293
- runtimeRow = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-mem-rb-muted", style: { marginTop: 8 }, children: "首次启用本地嵌入时会自动安装推理运行时(约 100~200MB)。" });
1294
- }
1295
- let applyRow = null;
1296
- if (ap.phase === "warming") {
1297
- applyRow = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-mem-rb-muted", style: { marginTop: 8 }, children: "加载嵌入模型中…(首次需数秒)" });
1298
- } else if (ap.phase === "switching") {
1299
- applyRow = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-mem-rb-muted", style: { marginTop: 8 }, children: "切换嵌入源中…" });
1300
- } else if (ap.phase === "error") {
1301
- applyRow = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { marginTop: 8, fontSize: 12, color: "var(--dsh-mem-danger)" }, children: "切换失败:" + ap.message + "(已保存的嵌入源不变,重启后仍按原源运行)" });
1302
- } else if (st.reindex && st.reindex.running) {
1303
- const rj = st.reindex;
1304
- const rDone = rj.l1Done + rj.l0Done;
1305
- const rTotal = rj.l1Total + rj.l0Total;
1306
- const rPct = rTotal > 0 ? Math.round(rDone / rTotal * 100) : 0;
1307
- applyRow = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { marginTop: 8 }, children: [
1308
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: S.flexRow, children: [
1309
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dsh-mem-rb-muted", children: "重嵌入中 L1 " + rj.l1Done + "/" + rj.l1Total + " · L0 " + rj.l0Done + "/" + rj.l0Total + "(" + rPct + "%)" }),
1310
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: S.grow }),
1311
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(NButton, { onClick: () => call("dsh-memory/embedding-reindex-cancel", {}), children: "取消" })
1312
- ] }),
1313
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { ...S.flexRow, marginTop: 6 }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-mem-rb-bar", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-mem-rb-fill", style: { width: rPct + "%" } }) }) })
1314
- ] });
1315
- }
1316
- const modelCards = st.models.map((m) => {
1317
- const isActive = st.source === "local" && st.activeModel === m.id;
1318
- const mDl = dlActive && dl.modelId === m.id;
1319
- const pct = mDl && dl.overallTotal > 0 ? Math.round(dl.overallReceived / dl.overallTotal * 100) : 0;
1320
- let action = null;
1321
- if (mDl) {
1322
- action = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { flex: 1, minWidth: 200 }, children: [
1323
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: S.flexRow, children: [
1324
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dsh-mem-rb-muted", style: { whiteSpace: "nowrap" }, children: (dl.phase === "verifying" ? "校验中 " : "") + fmtMB(dl.overallReceived) + " / " + fmtMB(dl.overallTotal) + "(文件 " + dl.fileIndex + "/" + dl.fileCount + "," + pct + "%" + (dl.speedBps > 0 && dl.phase === "downloading" ? "," + fmtMB(dl.speedBps) + "/s" : "") + ")" }),
1325
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: S.grow }),
1326
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(NButton, { onClick: () => call("dsh-memory/embedding-download-cancel", {}), children: "取消" })
1327
- ] }),
1328
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { ...S.flexRow, marginTop: 6 }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-mem-rb-bar", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-mem-rb-fill", style: { width: pct + "%" } }) }) })
1329
- ] });
1330
- } else if (isActive) {
1331
- action = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: S.flexRow, children: [
1332
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dsh-mem-tag dsh-mem-tag-work-task", children: "使用中" }),
1333
- localInfo && localInfo.state === "loading" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dsh-mem-rb-muted", children: "模型加载中…" }) : null,
1334
- localInfo && localInfo.state === "failed" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { fontSize: 12, color: "var(--dsh-mem-danger)" }, children: "加载失败:" + (localInfo.error || "") }) : null,
1335
- localInfo && localInfo.state === "ready" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dsh-mem-rb-muted", children: "已就绪" }) : null
1336
- ] });
1337
- } else if (m.state === "downloaded") {
1338
- action = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: S.flexRow, children: [
1339
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1340
- NButton,
1341
- {
1342
- disabled: ap.busy,
1343
- onClick: () => call("dsh-memory/embedding-source-set", { source: "local", activeModel: m.id }, switchConfirm),
1344
- children: "启用"
1345
- }
1346
- ),
1347
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1348
- NButton,
1349
- {
1350
- disabled: dlActive,
1351
- onClick: () => call("dsh-memory/embedding-model-delete", { modelId: m.id }, "删除已下载的 " + m.name + "(" + fmtMB(m.totalBytes) + ")?"),
1352
- children: "删除"
1353
- }
1354
- )
1355
- ] });
1356
- } else {
1357
- action = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1358
- NButton,
1359
- {
1360
- disabled: dlActive || !st.ceilings.local,
1361
- title: !st.ceilings.local ? "部署已禁用本地嵌入模型" : "",
1362
- onClick: () => call("dsh-memory/embedding-download-start", { modelId: m.id }),
1363
- children: (m.state === "partial" ? "继续下载 " : "下载 ") + fmtMB(m.totalBytes)
1364
- }
1365
- );
1267
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
1268
+ e.preventDefault();
1269
+ if (!open) {
1270
+ setOpen(true);
1271
+ setIdx(indexOfValue());
1272
+ } else moveActive(e.key === "ArrowDown" ? 1 : -1);
1273
+ return;
1366
1274
  }
1367
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1368
- "div",
1275
+ if (!open && (e.key === "Enter" || e.key === " ")) {
1276
+ e.preventDefault();
1277
+ setOpen(true);
1278
+ setIdx(indexOfValue());
1279
+ return;
1280
+ }
1281
+ if (open && e.key === "Enter") {
1282
+ e.preventDefault();
1283
+ let t = idx >= 0 ? idx : indexOfValue();
1284
+ if (t < 0) t = 0;
1285
+ if (options[t]) pick(options[t].id);
1286
+ }
1287
+ };
1288
+ const onBlur = (e) => {
1289
+ if (!open) return;
1290
+ const to = e.relatedTarget;
1291
+ if (!to || wrapRef.current && !wrapRef.current.contains(to)) setOpen(false);
1292
+ };
1293
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dsh-mem-sel", style: props.style, ref: wrapRef, onKeyDown: onKey, onBlur, children: [
1294
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1295
+ "button",
1369
1296
  {
1370
- style: { ...S.flexRow, padding: "8px 0", borderBottom: "1px solid var(--dsh-mem-border)", flexWrap: "wrap" },
1297
+ type: "button",
1298
+ className: "dsh-mem-select" + (open ? " dsh-mem-select-open" : ""),
1299
+ disabled,
1300
+ "aria-haspopup": "listbox",
1301
+ "aria-expanded": open,
1302
+ onClick: () => {
1303
+ if (open) closeMenu(false);
1304
+ else {
1305
+ setOpen(true);
1306
+ setIdx(-1);
1307
+ }
1308
+ },
1371
1309
  children: [
1372
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { minWidth: 150 }, children: [
1373
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { fontWeight: 600 }, children: m.name }),
1374
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-mem-rb-muted", children: m.tags.join(" · ") + " · " + m.dims + " 维 · 上下文 " + m.contextTokens })
1375
- ] }),
1376
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { flex: 1, minWidth: 180, fontSize: 12, color: "var(--dsh-mem-text-2)" }, children: m.description }),
1377
- action
1378
- ]
1379
- },
1380
- m.id
1381
- );
1382
- });
1383
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dsh-mem-rb-card", children: [
1384
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: S.flexRow, children: [
1385
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { fontWeight: 600, whiteSpace: "nowrap" }, children: "语义检索(嵌入源)" }),
1386
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: S.grow }),
1387
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1388
- Segmented,
1389
- {
1390
- value: st.source,
1391
- options: [
1392
- { key: "off", label: "关闭" },
1393
- { key: "local", label: "本地", disabled: !st.ceilings.local, disabledTitle: "部署已禁用本地嵌入模型" },
1394
- { key: "remote", label: "远程", disabled: !st.ceilings.remote, disabledTitle: "部署未配置远程嵌入(baseUrl/apiKey/model/dimensions)" }
1395
- ],
1396
- onChange: onSource
1397
- }
1398
- )
1399
- ] }),
1400
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-mem-rb-muted", style: { marginTop: 4 }, children: st.source === "off" ? "当前:关键词(BM25)检索,不做向量嵌入" : st.source === "remote" ? "当前:远程嵌入(" + (rt ? "" : "") + "模型由部署配置给定)" : "当前:本地嵌入" + (st.activeModel ? "(" + st.activeModel + ")" : "") }),
1401
- st.activeNote ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { marginTop: 4, fontSize: 12, color: "var(--dsh-mem-danger)" }, children: st.activeNote }) : null,
1402
- err && err !== "__unsupported__" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { marginTop: 6, fontSize: 12, color: "var(--dsh-mem-danger)" }, children: err }) : null,
1403
- dl && dl.phase === "error" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { marginTop: 6, fontSize: 12, color: "var(--dsh-mem-danger)" }, children: "下载失败:" + (dl.error || "") }) : null,
1404
- runtimeRow,
1405
- applyRow,
1406
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: S.panelLabel, children: "本地模型目录(下载后离线可用,不随插件分发)" }),
1407
- modelCards
1408
- ] });
1409
- }
1410
-
1411
- // client/src/tabs/RebuildPanel.tsx
1412
- var import_react6 = require("react");
1413
- var import_jsx_runtime6 = require("react/jsx-runtime");
1414
- var RB_PHASE_LABEL = {
1415
- preparing: "准备中(归档旧数据 · 清空检索库)",
1416
- distilling: "分块蒸馏中",
1417
- finalizing: "收尾(强制 L2 场景 + L3 画像)"
1418
- };
1419
- function RebuildPanel(props) {
1420
- const rpc = props.rpc;
1421
- const [rbRaw, setRb] = (0, import_react6.useState)(null);
1422
- const [confirmOpen, setConfirmOpen] = (0, import_react6.useState)(false);
1423
- const [busy, setBusy] = (0, import_react6.useState)(false);
1424
- const [rbError, setRbError] = (0, import_react6.useState)(null);
1425
- const refresh = (0, import_react6.useCallback)(() => {
1426
- rpc("dsh-memory/rebuild-status", {}).then((r) => {
1427
- if (r && r.ok) setRb(r.value);
1428
- }).catch(() => {
1429
- });
1430
- }, [rpc]);
1431
- (0, import_react6.useEffect)(() => {
1432
- refresh();
1433
- }, [refresh]);
1434
- const running = !!(rbRaw && rbRaw.running);
1435
- (0, import_react6.useEffect)(() => {
1436
- if (!running) return;
1437
- const timer = setInterval(refresh, 1500);
1438
- return () => {
1439
- clearInterval(timer);
1440
- };
1441
- }, [running, refresh]);
1442
- if (!rbRaw || rbRaw.supported === false) return null;
1443
- const rb = rbRaw;
1444
- ensureThemeStyle();
1445
- const start = () => {
1446
- setBusy(true);
1447
- setRbError(null);
1448
- rpc("dsh-memory/rebuild-start", {}).then((r) => {
1449
- setBusy(false);
1450
- if (r && r.ok) {
1451
- setConfirmOpen(false);
1452
- setRb(r.value);
1453
- } else {
1454
- setRbError(r && r.error ? r.error.message : "启动失败");
1455
- }
1456
- }).catch((e) => {
1457
- setBusy(false);
1458
- setRbError(String(e && e.message || e));
1459
- });
1460
- };
1461
- const cancel = () => {
1462
- setBusy(true);
1463
- rpc("dsh-memory/rebuild-cancel", {}).then((r) => {
1464
- setBusy(false);
1465
- if (r && r.ok) setRb(r.value);
1466
- }).catch(() => {
1467
- setBusy(false);
1468
- });
1469
- };
1470
- const empty = !running && (rb.messageCount === 0 || rb.estCalls === 0);
1471
- const pct = rb.total > 0 ? Math.round(rb.done / rb.total * 100) : 0;
1472
- let lastNote = null;
1473
- if (!running && rb.phase === "done") {
1474
- lastNote = "上次重建:完成(" + rb.done + "/" + rb.total + " 会话,产出 " + rb.recordsBuilt + " 条记录)" + (rb.finishedAt ? " · " + fmtTime(new Date(rb.finishedAt).toISOString()) : "");
1475
- } else if (!running && rb.phase === "cancelled") {
1476
- lastNote = "上次重建:已取消(完成 " + rb.done + "/" + rb.total + " 会话,已重建部分保留)";
1477
- } else if (!running && rb.phase === "failed") {
1478
- lastNote = "上次重建:失败:" + (rb.error || "未知错误");
1479
- }
1480
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dsh-mem-rb-card", children: [
1481
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }, children: [
1482
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { fontWeight: 600, whiteSpace: "nowrap" }, children: "重建记忆" }),
1483
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "dsh-mem-rb-muted", style: { flex: 1, minWidth: 180 }, children: running ? (RB_PHASE_LABEL[rb.phase] || rb.phase) + " · " + rb.done + "/" + rb.total + " 会话(" + pct + "%)" : "从 L0 原始对话重新蒸馏 L1/L2/L3;旧数据先归档(不删除)" }),
1484
- running ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(NButton, { disabled: busy || rb.cancelRequested, onClick: cancel, children: rb.cancelRequested ? "取消中…" : "取消重建" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1485
- NButton,
1486
- {
1487
- disabled: busy || empty,
1488
- title: empty ? "L0 无消息,无可重建内容" : "重新蒸馏全部记忆",
1489
- style: { color: "var(--dsh-mem-danger)" },
1490
- onClick: () => {
1491
- setConfirmOpen(true);
1492
- },
1493
- children: busy ? "…" : "开始重建"
1494
- }
1495
- )
1496
- ] }),
1497
- running ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 10, marginTop: 10 }, children: [
1498
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "dsh-mem-rb-bar", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "dsh-mem-rb-fill", style: { width: pct + "%" } }) }),
1499
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dsh-mem-rb-muted", style: { whiteSpace: "nowrap" }, children: "产出 " + rb.recordsBuilt + " 条" })
1500
- ] }) : null,
1501
- lastNote ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "dsh-mem-rb-muted", style: { marginTop: 8 }, children: lastNote }) : null,
1502
- rbError ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { marginTop: 8, fontSize: 12, color: "var(--dsh-mem-danger)" }, children: rbError }) : null,
1503
- confirmOpen ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1504
- NModal,
1505
- {
1506
- open: true,
1507
- onClose: () => {
1508
- setConfirmOpen(false);
1509
- },
1510
- title: "确认重建全部记忆?",
1511
- footer: [
1512
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1513
- NButton,
1514
- {
1515
- onClick: () => {
1516
- setConfirmOpen(false);
1517
- },
1518
- children: "取消"
1519
- },
1520
- "cancel"
1521
- ),
1522
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(NButton, { variant: "primary", disabled: busy, onClick: start, children: busy ? "启动中…" : "开始重建" }, "confirm")
1523
- ],
1524
- children: [
1525
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
1526
- "将以 L0 原始对话为事实源重新蒸馏:",
1527
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("b", { children: rb.sessionCount + " 个会话 · " + rb.messageCount + " 条消息" }),
1528
- ",预计 ≥" + rb.estCalls + " 次蒸馏调用。"
1529
- ] }),
1530
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { marginTop: 8 }, children: "现有 L1 记忆 / L2 场景 / L3 画像会整体归档(*.bak.时间戳,可手工找回),随后清空重建;重建期间可正常对话,新对话的蒸馏优先进行;中途可取消,已重建部分保留。" })
1531
- ]
1532
- }
1533
- ) : null
1534
- ] });
1535
- }
1536
-
1537
- // client/src/tabs/RouteChainEditor.tsx
1538
- var import_react8 = require("react");
1539
-
1540
- // client/src/ui/NSel.tsx
1541
- var import_react7 = require("react");
1542
- var import_jsx_runtime7 = require("react/jsx-runtime");
1543
- function NSel(props) {
1544
- const options = props.options || [];
1545
- const value = props.value || "";
1546
- const disabled = !!props.disabled;
1547
- const [open, setOpen] = (0, import_react7.useState)(false);
1548
- const [idx, setIdx] = (0, import_react7.useState)(-1);
1549
- const wrapRef = (0, import_react7.useRef)(null);
1550
- const listRef = (0, import_react7.useRef)(null);
1551
- let selectedLabel = "";
1552
- for (let si = 0; si < options.length; si++) {
1553
- if (options[si].id === value) selectedLabel = options[si].label;
1554
- }
1555
- (0, import_react7.useEffect)(() => {
1556
- if (!open) return void 0;
1557
- const onDown = (e) => {
1558
- if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
1559
- };
1560
- document.addEventListener("mousedown", onDown);
1561
- return () => {
1562
- document.removeEventListener("mousedown", onDown);
1563
- };
1564
- }, [open]);
1565
- (0, import_react7.useEffect)(() => {
1566
- if (!open || !listRef.current) return;
1567
- const el = listRef.current.querySelector('[data-active="1"]');
1568
- if (el && el.scrollIntoView) el.scrollIntoView({ block: "nearest" });
1569
- }, [open, idx]);
1570
- const indexOfValue = () => {
1571
- for (let i = 0; i < options.length; i++) if (options[i].id === value) return i;
1572
- return -1;
1573
- };
1574
- const closeMenu = (refocus) => {
1575
- setOpen(false);
1576
- setIdx(-1);
1577
- if (refocus && wrapRef.current) {
1578
- const btn = wrapRef.current.querySelector("button");
1579
- if (btn && btn.focus) btn.focus();
1580
- }
1581
- };
1582
- const pick = (id) => {
1583
- closeMenu(true);
1584
- if (id !== value && props.onChange) props.onChange(id);
1585
- };
1586
- const moveActive = (delta) => {
1587
- const n = options.length;
1588
- if (n === 0) return;
1589
- let cur = idx >= 0 ? idx : indexOfValue();
1590
- if (cur < 0) cur = delta > 0 ? -1 : 0;
1591
- setIdx(delta > 0 ? ((cur + 1) % n + n) % n : ((cur - 1) % n + n) % n);
1592
- };
1593
- const onKey = (e) => {
1594
- if (disabled) return;
1595
- if (e.key === "Escape") {
1596
- if (open) {
1597
- e.preventDefault();
1598
- closeMenu(true);
1599
- }
1600
- return;
1601
- }
1602
- if (e.key === "ArrowDown" || e.key === "ArrowUp") {
1603
- e.preventDefault();
1604
- if (!open) {
1605
- setOpen(true);
1606
- setIdx(indexOfValue());
1607
- } else moveActive(e.key === "ArrowDown" ? 1 : -1);
1608
- return;
1609
- }
1610
- if (!open && (e.key === "Enter" || e.key === " ")) {
1611
- e.preventDefault();
1612
- setOpen(true);
1613
- setIdx(indexOfValue());
1614
- return;
1615
- }
1616
- if (open && e.key === "Enter") {
1617
- e.preventDefault();
1618
- let t = idx >= 0 ? idx : indexOfValue();
1619
- if (t < 0) t = 0;
1620
- if (options[t]) pick(options[t].id);
1621
- }
1622
- };
1623
- const onBlur = (e) => {
1624
- if (!open) return;
1625
- const to = e.relatedTarget;
1626
- if (!to || wrapRef.current && !wrapRef.current.contains(to)) setOpen(false);
1627
- };
1628
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-mem-sel", style: props.style, ref: wrapRef, onKeyDown: onKey, onBlur, children: [
1629
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1630
- "button",
1631
- {
1632
- type: "button",
1633
- className: "dsh-mem-select" + (open ? " dsh-mem-select-open" : ""),
1634
- disabled,
1635
- "aria-haspopup": "listbox",
1636
- "aria-expanded": open,
1637
- onClick: () => {
1638
- if (open) closeMenu(false);
1639
- else {
1640
- setOpen(true);
1641
- setIdx(-1);
1642
- }
1643
- },
1644
- children: [
1645
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-mem-select-label", children: selectedLabel || props.placeholder || "(请选择)" }),
1646
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-mem-sel-chev" + (open ? " dsh-mem-sel-chev-open" : ""), "aria-hidden": true })
1310
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dsh-mem-select-label", children: selectedLabel || props.placeholder || "(请选择)" }),
1311
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dsh-mem-sel-chev" + (open ? " dsh-mem-sel-chev-open" : ""), "aria-hidden": true })
1647
1312
  ]
1648
1313
  }
1649
1314
  ),
1650
- open && !disabled ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "dsh-mem-pop", ref: listRef, role: "listbox", children: options.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "dsh-mem-pop-empty", children: "无选项" }) : options.map((o, i) => {
1651
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1315
+ open && !disabled ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-mem-pop", ref: listRef, role: "listbox", children: options.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dsh-mem-pop-empty", children: "无选项" }) : options.map((o, i) => {
1316
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1652
1317
  "button",
1653
1318
  {
1654
1319
  type: "button",
@@ -1666,8 +1331,8 @@ var __defProp = Object.defineProperty;
1666
1331
  if (idx !== i) setIdx(i);
1667
1332
  },
1668
1333
  children: [
1669
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-mem-pop-opt-label", children: o.label }),
1670
- o.id === value ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-mem-pop-check", children: "✓" }) : null
1334
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dsh-mem-pop-opt-label", children: o.label }),
1335
+ o.id === value ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dsh-mem-pop-check", children: "✓" }) : null
1671
1336
  ]
1672
1337
  },
1673
1338
  o.id
@@ -1677,7 +1342,7 @@ var __defProp = Object.defineProperty;
1677
1342
  }
1678
1343
 
1679
1344
  // client/src/tabs/RouteChainEditor.tsx
1680
- var import_jsx_runtime8 = require("react/jsx-runtime");
1345
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1681
1346
  var modelsCache = {};
1682
1347
  var EFFORT_VOCAB = ["", "off", "none", "minimal", "low", "medium", "high", "xhigh", "max"];
1683
1348
  var STY = {
@@ -1704,26 +1369,28 @@ var __defProp = Object.defineProperty;
1704
1369
  function RouteChainEditor(props) {
1705
1370
  const rpc = props.rpc;
1706
1371
  const disabled = !!props.disabled;
1707
- const [info, setInfo] = (0, import_react8.useState)(null);
1708
- const [rows, setRows] = (0, import_react8.useState)(null);
1709
- const [rowErrs, setRowErrs] = (0, import_react8.useState)({});
1710
- const [err, setErr] = (0, import_react8.useState)(null);
1711
- const [manual, setManual] = (0, import_react8.useState)({ idx: -1, text: "" });
1712
- const pendingWrites = (0, import_react8.useRef)(0);
1372
+ const scope = props.scope ?? "global";
1373
+ const isLayer = scope !== "global";
1374
+ const [info, setInfo] = (0, import_react6.useState)(null);
1375
+ const [rows, setRows] = (0, import_react6.useState)(null);
1376
+ const [rowErrs, setRowErrs] = (0, import_react6.useState)({});
1377
+ const [err, setErr] = (0, import_react6.useState)(null);
1378
+ const [manual, setManual] = (0, import_react6.useState)({ idx: -1, text: "" });
1379
+ const pendingWrites = (0, import_react6.useRef)(0);
1713
1380
  function refreshInfo() {
1714
1381
  rpc("dsh-memory/llm-providers", {}).then((r) => {
1715
1382
  if (r && r.ok && pendingWrites.current === 0) setInfo(r.value);
1716
1383
  }).catch(() => {
1717
1384
  });
1718
1385
  }
1719
- (0, import_react8.useEffect)(() => {
1386
+ (0, import_react6.useEffect)(() => {
1720
1387
  refreshInfo();
1721
1388
  const timer = setInterval(refreshInfo, 5e3);
1722
1389
  return () => {
1723
1390
  clearInterval(timer);
1724
1391
  };
1725
1392
  }, [rpc]);
1726
- (0, import_react8.useEffect)(() => {
1393
+ (0, import_react6.useEffect)(() => {
1727
1394
  if (!info || !info.providers) return;
1728
1395
  info.providers.forEach((p) => {
1729
1396
  if (!p.id || modelsCache[p.id]) return;
@@ -1733,11 +1400,13 @@ var __defProp = Object.defineProperty;
1733
1400
  });
1734
1401
  });
1735
1402
  }, [rpc, info]);
1736
- (0, import_react8.useEffect)(() => {
1737
- if (rows === null && info && info.chain && info.chain.current.length) {
1738
- setRows(info.chain.current.map(copyRow));
1403
+ const layerView = isLayer && info && info.layerChains ? info.layerChains[scope] : null;
1404
+ const savedRows = isLayer ? layerView?.runtime ?? [] : info && info.chain ? info.chain.current : [];
1405
+ (0, import_react6.useEffect)(() => {
1406
+ if (rows === null && info && savedRows.length) {
1407
+ setRows(savedRows.map(copyRow));
1739
1408
  }
1740
- }, [info, rows]);
1409
+ }, [info, rows, savedRows]);
1741
1410
  function updateRow(i, patch) {
1742
1411
  setRows(rows.map((r, j) => j === i ? { ...r, ...patch } : r));
1743
1412
  setRowErrs({});
@@ -1764,8 +1433,12 @@ var __defProp = Object.defineProperty;
1764
1433
  setRowErrs({});
1765
1434
  }
1766
1435
  function removeRow(i) {
1767
- if (i === 0) setRows([EMPTY_ROW].concat(rows.slice(1)));
1768
- else setRows(rows.slice(0, i).concat(rows.slice(i + 1)));
1436
+ if (i === 0) {
1437
+ if (isLayer) setRows(rows.length > 1 ? rows.slice(1) : rows);
1438
+ else setRows([EMPTY_ROW].concat(rows.slice(1)));
1439
+ } else {
1440
+ setRows(rows.slice(0, i).concat(rows.slice(i + 1)));
1441
+ }
1769
1442
  setRowErrs({});
1770
1443
  }
1771
1444
  function addRow() {
@@ -1775,6 +1448,16 @@ var __defProp = Object.defineProperty;
1775
1448
  setRowErrs({});
1776
1449
  }
1777
1450
  function forkStatic() {
1451
+ if (isLayer) {
1452
+ const st2 = layerView && layerView.static || [];
1453
+ if (st2.length) setRows(st2.slice(0, 8).map(copyRow));
1454
+ else {
1455
+ const first = info.providers && info.providers[0] || { id: "" };
1456
+ setRows([{ provider: first.id, model: "", reasoningEffort: "" }]);
1457
+ }
1458
+ setRowErrs({});
1459
+ return;
1460
+ }
1778
1461
  const st = info.chain && info.chain.static || [];
1779
1462
  setRows([EMPTY_ROW].concat(st.slice(0, 7).map(copyRow)));
1780
1463
  setRowErrs({});
@@ -1782,7 +1465,9 @@ var __defProp = Object.defineProperty;
1782
1465
  function save() {
1783
1466
  const errs = {};
1784
1467
  const seen = {};
1785
- if (rows[0].provider && !rows[0].model || !rows[0].provider && rows[0].model) {
1468
+ if (isLayer && (!rows[0].provider || !rows[0].model)) {
1469
+ errs[0] = "主路由行必须显式选择供应商与模型(层链不支持跟随默认模型)";
1470
+ } else if (rows[0].provider && !rows[0].model || !rows[0].provider && rows[0].model) {
1786
1471
  errs[0] = "主路由行供应商与模型须成对(双空 = 跟随默认模型)";
1787
1472
  }
1788
1473
  if (rows[0].provider && rows[0].model) seen[rows[0].provider + "::" + rows[0].model] = 0;
@@ -1805,11 +1490,22 @@ var __defProp = Object.defineProperty;
1805
1490
  }
1806
1491
  if (Object.keys(errs).length) return;
1807
1492
  pendingWrites.current += 1;
1808
- setInfo({
1809
- ...info,
1810
- chain: { ...info.chain, current: rows.map(copyRow), source: "runtime" }
1811
- });
1812
- rpc("dsh-memory/settings-set", { distillChain: rows }).then((r) => {
1493
+ if (isLayer && info && info.layerChains) {
1494
+ setInfo({
1495
+ ...info,
1496
+ layerChains: {
1497
+ ...info.layerChains,
1498
+ [scope]: { ...info.layerChains[scope], runtime: rows.map(copyRow), source: "runtime" }
1499
+ }
1500
+ });
1501
+ } else {
1502
+ setInfo({
1503
+ ...info,
1504
+ chain: { ...info.chain, current: rows.map(copyRow), source: "runtime" }
1505
+ });
1506
+ }
1507
+ const payload = isLayer ? { distillLayerChains: { [scope]: rows } } : { distillChain: rows };
1508
+ rpc("dsh-memory/settings-set", payload).then((r) => {
1813
1509
  pendingWrites.current -= 1;
1814
1510
  setErr(!r || r.ok ? null : "路由链保存失败:" + (r && r.error && r.error.message || "未知错误"));
1815
1511
  refreshInfo();
@@ -1821,6 +1517,20 @@ var __defProp = Object.defineProperty;
1821
1517
  }
1822
1518
  function clearToFollow() {
1823
1519
  pendingWrites.current += 1;
1520
+ if (isLayer) {
1521
+ rpc("dsh-memory/settings-set", { distillLayerChains: { [scope]: [] } }).then((r) => {
1522
+ pendingWrites.current -= 1;
1523
+ setRows(null);
1524
+ setRowErrs({});
1525
+ setErr(!r || r.ok ? null : "清空失败,请重试");
1526
+ refreshInfo();
1527
+ }).catch((e) => {
1528
+ pendingWrites.current -= 1;
1529
+ setErr("清空失败:" + String(e && e.message || e));
1530
+ refreshInfo();
1531
+ });
1532
+ return;
1533
+ }
1824
1534
  rpc("dsh-memory/settings-set", { distillChain: [], distillProvider: "", distillModel: "", reasoningEffort: "" }).then((r) => {
1825
1535
  pendingWrites.current -= 1;
1826
1536
  setRows(null);
@@ -1840,38 +1550,52 @@ var __defProp = Object.defineProperty;
1840
1550
  providersById[p.id] = p;
1841
1551
  });
1842
1552
  function roRow(e, i) {
1843
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: STY.roRow, children: [
1844
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: STY.badge, children: i === 0 ? "主" : String(i + 1) }),
1845
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: STY.mono, children: e.provider + " / " + e.model }),
1846
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { marginLeft: "auto", flexShrink: 0, fontSize: 11, color: "var(--dsh-mem-text-3)" }, children: e.effort ? "档位 " + e.effort : "跟随部署配置" })
1553
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: STY.roRow, children: [
1554
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: STY.badge, children: i === 0 ? "主" : String(i + 1) }),
1555
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { ...STY.mono, color: "var(--dsh-mem-text-2)" }, children: e.provider + " / " + e.model }),
1556
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { marginLeft: "auto", flexShrink: 0, fontSize: 11, color: "var(--dsh-mem-text-3)" }, children: e.effort ? "档位 " + e.effort : "跟随部署配置" })
1847
1557
  ] }, "ro" + i);
1848
1558
  }
1849
1559
  if (info.pinned) {
1850
- const effPin = info.chain && info.chain.effectiveChain || [];
1851
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: STY.wrap, children: [
1560
+ const effPin = isLayer ? layerView?.effectiveChain ?? [] : info.chain && info.chain.effectiveChain || [];
1561
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: STY.wrap, children: [
1852
1562
  effPin.map(roRow),
1853
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: STY.note, children: "部署已锁定路由(pin 优先于运行时链);调整请修改 profile cordis.patch.yml 中 llm 的配置。" })
1563
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: STY.note, children: "部署已锁定路由(pin),调整请修改 cordis.patch.yml 中 llm 的配置。" })
1854
1564
  ] });
1855
1565
  }
1856
1566
  if (rows === null) {
1567
+ if (isLayer) {
1568
+ const lv = layerView;
1569
+ const src = lv?.source ?? "global";
1570
+ if (src === "static" && lv) {
1571
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: STY.wrap, children: [
1572
+ lv.static.map((e, i) => roRow({ provider: e.provider, model: e.model, effort: e.reasoningEffort || "" }, i)),
1573
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { marginTop: 6 }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(NButton, { onClick: forkStatic, disabled, children: "自定义本层链" }) })
1574
+ ] });
1575
+ }
1576
+ const previewRows = lv?.effectiveChain ?? [];
1577
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: STY.wrap, children: [
1578
+ previewRows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: S.switchDesc, children: "本层跟随全局链,暂无可用路由。" }) : previewRows.map(roRow),
1579
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { marginTop: 6 }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(NButton, { onClick: forkStatic, disabled, children: "自定义本层链" }) })
1580
+ ] });
1581
+ }
1857
1582
  const effFollow = info.chain && info.chain.effectiveChain || [];
1858
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: STY.wrap, children: [
1859
- effFollow.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: S.switchDesc, children: "蒸馏跟随默认模型,未配置回退链。" }) : effFollow.map(roRow),
1860
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { marginTop: 6 }, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(NButton, { onClick: forkStatic, disabled, children: "编辑为运行时链" }) }),
1861
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: STY.note, children: "未配置运行时链时跟随部署配置;「编辑为运行时链」会拷贝部署回退链为草稿,保存起运行时链接管。" })
1583
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: STY.wrap, children: [
1584
+ effFollow.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: S.switchDesc, children: "蒸馏跟随默认模型,未配置回退链。" }) : effFollow.map(roRow),
1585
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { marginTop: 6 }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(NButton, { onClick: forkStatic, disabled, children: "编辑为运行时链" }) })
1862
1586
  ] });
1863
1587
  }
1864
1588
  const capped = rows.length >= 8;
1865
- const dirty = JSON.stringify(rows) !== JSON.stringify(info.chain && info.chain.current || []);
1866
- const rowEls = rows.map((row, i) => {
1589
+ const dirty = JSON.stringify(rows) !== JSON.stringify(savedRows);
1590
+ const rowEls = rows.map((row2, i) => {
1867
1591
  const isPrimary = i === 0;
1868
- const known = !row.provider || !!providersById[row.provider];
1869
- const modelsLoaded = row.provider ? Object.prototype.hasOwnProperty.call(modelsCache, row.provider) : false;
1870
- const modelList = modelsLoaded ? modelsCache[row.provider] : [];
1592
+ const known = !row2.provider || !!providersById[row2.provider];
1593
+ const modelsLoaded = row2.provider ? Object.prototype.hasOwnProperty.call(modelsCache, row2.provider) : false;
1594
+ const modelList = modelsLoaded ? modelsCache[row2.provider] : [];
1871
1595
  const manualInput = modelsLoaded && modelList.length === 0;
1872
1596
  let curEfforts = [];
1873
1597
  for (let mi = 0; mi < modelList.length; mi++) {
1874
- if (modelList[mi].id === row.model) {
1598
+ if (modelList[mi].id === row2.model) {
1875
1599
  curEfforts = modelList[mi].efforts || [];
1876
1600
  break;
1877
1601
  }
@@ -1879,7 +1603,7 @@ var __defProp = Object.defineProperty;
1879
1603
  let providerOptions = providers.map((p) => {
1880
1604
  return { id: p.id, label: p.name !== p.id ? p.name + "(" + p.id + ")" : p.id };
1881
1605
  });
1882
- if (isPrimary) {
1606
+ if (isPrimary && !isLayer) {
1883
1607
  providerOptions = [
1884
1608
  {
1885
1609
  id: "",
@@ -1887,27 +1611,27 @@ var __defProp = Object.defineProperty;
1887
1611
  }
1888
1612
  ].concat(providerOptions);
1889
1613
  }
1890
- if (row.provider && !providersById[row.provider]) {
1891
- providerOptions.push({ id: row.provider, label: row.provider + "(已不在列表)" });
1614
+ if (row2.provider && !providersById[row2.provider]) {
1615
+ providerOptions.push({ id: row2.provider, label: row2.provider + "(已不在列表)" });
1892
1616
  }
1893
1617
  const modelOptions = modelList.map((m) => {
1894
1618
  return { id: m.id, label: m.name !== m.id ? m.name + "(" + m.id + ")" : m.id };
1895
1619
  });
1896
- if (row.model && !modelList.some((m) => m.id === row.model)) {
1897
- modelOptions.push({ id: row.model, label: row.model + "(已不在列表)" });
1620
+ if (row2.model && !modelList.some((m) => m.id === row2.model)) {
1621
+ modelOptions.push({ id: row2.model, label: row2.model + "(已不在列表)" });
1898
1622
  }
1899
1623
  const effortOptions = [{ id: "", label: "跟随部署配置" }].concat(
1900
1624
  curEfforts.filter((k) => EFFORT_VOCAB.indexOf(k) >= 0).map((k) => ({ id: k, label: k }))
1901
1625
  );
1902
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { ...STY.row, ...rowErrs[i] ? STY.rowErr : null }, children: [
1903
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: STY.line, children: [
1904
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: STY.badge, children: isPrimary ? "主" : String(i + 1) }),
1905
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1626
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { ...STY.row, ...rowErrs[i] ? STY.rowErr : null }, children: [
1627
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: STY.line, children: [
1628
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: STY.badge, children: isPrimary ? "主" : String(i + 1) }),
1629
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1906
1630
  NSel,
1907
1631
  {
1908
1632
  style: { flex: 1, minWidth: 150 },
1909
1633
  options: providerOptions,
1910
- value: row.provider,
1634
+ value: row2.provider,
1911
1635
  disabled,
1912
1636
  placeholder: isPrimary ? "跟随默认模型" : "供应商",
1913
1637
  onChange: (v) => {
@@ -1915,12 +1639,12 @@ var __defProp = Object.defineProperty;
1915
1639
  }
1916
1640
  }
1917
1641
  ),
1918
- !row.provider ? null : manualInput ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1642
+ !row2.provider ? null : manualInput ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1919
1643
  NInput,
1920
1644
  {
1921
1645
  style: { flex: 1, minWidth: 150 },
1922
1646
  placeholder: "模型 id(该供应商未提供列表,输入后回车)…",
1923
- value: manual.idx === i ? manual.text : row.model,
1647
+ value: manual.idx === i ? manual.text : row2.model,
1924
1648
  onChange: (e) => {
1925
1649
  setManual({ idx: i, text: e.target.value });
1926
1650
  },
@@ -1934,12 +1658,12 @@ var __defProp = Object.defineProperty;
1934
1658
  }
1935
1659
  }
1936
1660
  }
1937
- ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1661
+ ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1938
1662
  NSel,
1939
1663
  {
1940
1664
  style: { flex: 1, minWidth: 150 },
1941
1665
  options: modelOptions,
1942
- value: row.model,
1666
+ value: row2.model,
1943
1667
  disabled: disabled || !modelsLoaded,
1944
1668
  placeholder: modelsLoaded ? isPrimary ? "(选择模型,可留空跟随默认)" : "(选择模型)" : "加载模型列表…",
1945
1669
  onChange: (v) => {
@@ -1947,13 +1671,13 @@ var __defProp = Object.defineProperty;
1947
1671
  }
1948
1672
  }
1949
1673
  ),
1950
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1674
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1951
1675
  NSel,
1952
1676
  {
1953
1677
  style: { flexShrink: 0, width: 118 },
1954
1678
  options: effortOptions,
1955
- value: row.reasoningEffort,
1956
- disabled: disabled || !(row.provider && row.model),
1679
+ value: row2.reasoningEffort,
1680
+ disabled: disabled || !(row2.provider && row2.model),
1957
1681
  placeholder: "跟随部署配置",
1958
1682
  onChange: (v) => {
1959
1683
  updateRow(i, { reasoningEffort: v });
@@ -1961,8 +1685,8 @@ var __defProp = Object.defineProperty;
1961
1685
  }
1962
1686
  )
1963
1687
  ] }),
1964
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: STY.actions, children: [
1965
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1688
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: STY.actions, children: [
1689
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1966
1690
  NButton,
1967
1691
  {
1968
1692
  style: STY.ico,
@@ -1974,11 +1698,11 @@ var __defProp = Object.defineProperty;
1974
1698
  children: "↑"
1975
1699
  }
1976
1700
  ),
1977
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1701
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1978
1702
  NButton,
1979
1703
  {
1980
1704
  style: STY.ico,
1981
- disabled: disabled || i === rows.length - 1 || i === 0 && !row.provider,
1705
+ disabled: disabled || i === rows.length - 1 || i === 0 && !row2.provider,
1982
1706
  title: "下移",
1983
1707
  onClick: () => {
1984
1708
  moveRow(i, 1);
@@ -1986,7 +1710,7 @@ var __defProp = Object.defineProperty;
1986
1710
  children: "↓"
1987
1711
  }
1988
1712
  ),
1989
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1713
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1990
1714
  NButton,
1991
1715
  {
1992
1716
  style: STY.ico,
@@ -1999,43 +1723,525 @@ var __defProp = Object.defineProperty;
1999
1723
  }
2000
1724
  )
2001
1725
  ] }),
2002
- isPrimary && !row.provider ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: STY.note, children: "跟随默认模型" + (info.default ? ":" + info.default.provider + " / " + info.default.model : "") + "(档位跟随部署配置,选定模型后可单独设置)" }) : null,
2003
- !known ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: STY.warn, children: "⚠ 供应商 " + row.provider + " 已不在已注册路由中:该路由调用会失败并被链跳过(不阻止保存)。" }) : null,
2004
- rowErrs[i] ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: STY.warn, children: "✕ " + rowErrs[i] }) : null
1726
+ isPrimary && !row2.provider ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: STY.note, children: "跟随默认模型" + (info.default ? ":" + info.default.provider + " / " + info.default.model : "") + "(档位跟随部署配置,选定模型后可单独设置)" }) : null,
1727
+ !known ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: STY.warn, children: "⚠ 供应商 " + row2.provider + " 已不在已注册路由中:该路由调用会失败并被链跳过(不阻止保存)。" }) : null,
1728
+ rowErrs[i] ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: STY.warn, children: "✕ " + rowErrs[i] }) : null
2005
1729
  ] }, "row" + i);
2006
1730
  });
2007
- const effChain = info.chain && info.chain.effectiveChain || [];
2008
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: STY.wrap, children: [
2009
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { ...S.switchDesc, marginBottom: 8 }, children: "路由按序尝试:第 1 行是主路由,失败(报错 / 掐断 / 网络异常 / 空输出)后按序降级;每行档位独立,缺省跟随部署配置。" }),
2010
- rowEls,
2011
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(NButton, { style: STY.add, disabled: disabled || capped, onClick: addRow, children: capped ? "已达上限(8 条)" : "+ 添加回退路由" }),
2012
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 10 }, children: [
2013
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(NButton, { style: STY.ghost, disabled, onClick: clearToFollow, children: "清空并跟随部署配置" }),
1731
+ const effChain = isLayer ? layerView?.effectiveChain ?? [] : info.chain && info.chain.effectiveChain || [];
1732
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: STY.wrap, children: [
1733
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1734
+ "div",
1735
+ {
1736
+ style: { ...S.switchDesc, marginBottom: 8 },
1737
+ title: isLayer ? "本层链失败只在层内降级,绝不落到全局链;每行档位独立" : "第 1 行主路由;失败(报错/掐断/网络异常/空输出)按序降级;每行档位独立",
1738
+ children: isLayer ? "主路由失败,只降级到本层回退" : "主路由失败,按序降级"
1739
+ }
1740
+ ),
1741
+ rowEls,
1742
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(NButton, { style: STY.add, disabled: disabled || capped, onClick: addRow, children: capped ? "已达上限(8 条)" : "+ 添加回退路由" }),
1743
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 10 }, children: [
1744
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1745
+ NButton,
1746
+ {
1747
+ style: isLayer ? { ...STY.ghost, color: "var(--dsh-mem-danger)", border: "1px solid var(--dsh-mem-danger)" } : STY.ghost,
1748
+ disabled,
1749
+ onClick: clearToFollow,
1750
+ children: isLayer ? "清除自定义 · 跟随全局" : "清空并跟随部署配置"
1751
+ }
1752
+ ),
1753
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: S.grow }),
1754
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(NButton, { variant: "primary", disabled, onClick: save, children: "保存" })
1755
+ ] }),
1756
+ err ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { ...STY.warn, marginTop: 8 }, children: "✕ " + err }) : null,
1757
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { marginTop: 10, paddingTop: 10, borderTop: "1px solid var(--dsh-mem-border)" }, children: [
1758
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { fontSize: 11, color: "var(--dsh-mem-text-3)", marginBottom: 4 }, children: "实际链" + (dirty ? "(保存后更新;当前显示已保存值)" : "") }),
1759
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1760
+ "div",
1761
+ {
1762
+ style: { fontSize: 12, color: "var(--dsh-mem-text-2)", wordBreak: "break-all", fontFamily: "ui-monospace, SFMono-Regular, Consolas, monospace" },
1763
+ children: effChain.map((e) => e.provider + "/" + e.model + (e.effort ? "(" + e.effort + ")" : "")).join(" → ") || "(暂无可用路由)"
1764
+ }
1765
+ )
1766
+ ] })
1767
+ ] });
1768
+ }
1769
+
1770
+ // client/src/tabs/DistillSettings.tsx
1771
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1772
+ var STY2 = {
1773
+ hint: { fontSize: 11, color: "var(--dsh-mem-text-3)", margin: "0 0 8px" },
1774
+ panelHead: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", marginBottom: 8 },
1775
+ panelTitle: { fontSize: 13.5, fontWeight: 650, color: "var(--dsh-mem-text-1)" },
1776
+ chip: {
1777
+ display: "inline-flex",
1778
+ alignItems: "center",
1779
+ borderRadius: 999,
1780
+ padding: "1px 8px",
1781
+ fontSize: 11,
1782
+ fontWeight: 600,
1783
+ lineHeight: "18px",
1784
+ whiteSpace: "nowrap"
1785
+ },
1786
+ chipAccent: { background: "var(--dsh-mem-accent-weak)", color: "var(--dsh-mem-accent-text)" },
1787
+ chipMuted: { background: "var(--dsh-mem-bg-inset)", color: "var(--dsh-mem-text-2)" },
1788
+ inUse: { fontSize: 11, color: "var(--dsh-mem-text-3)", margin: "6px 0 0" }
1789
+ };
1790
+ function Dot(props) {
1791
+ const base = {
1792
+ display: "inline-block",
1793
+ width: 7,
1794
+ height: 7,
1795
+ borderRadius: "50%",
1796
+ marginRight: 4,
1797
+ verticalAlign: "middle",
1798
+ flexShrink: 0
1799
+ };
1800
+ if (props.kind === "runtime") return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { ...base, background: "var(--dsh-mem-accent)" } });
1801
+ if (props.kind === "static") return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { ...base, background: "transparent", border: "1.5px solid var(--dsh-mem-text-3)" } });
1802
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { ...base, background: "var(--dsh-mem-track)" } });
1803
+ }
1804
+ var LAYER_META = {
1805
+ l1: { seg: "L1", title: "L1 · 抽取 / 去重" },
1806
+ l2: { seg: "L2", title: "L2 · 场景摘要" },
1807
+ l3: { seg: "L3", title: "L3 · 画像蒸馏" }
1808
+ };
1809
+ function DistillSettings(props) {
1810
+ const rpc = props.rpc;
1811
+ const disabled = !!props.disabled;
1812
+ const [info, setInfo] = (0, import_react7.useState)(null);
1813
+ const [tab, setTab] = (0, import_react7.useState)("g");
1814
+ (0, import_react7.useEffect)(() => {
1815
+ let alive = true;
1816
+ const refresh = () => {
1817
+ rpc("dsh-memory/llm-providers", {}).then((r) => {
1818
+ if (alive && r && r.ok) setInfo(r.value);
1819
+ }).catch(() => {
1820
+ });
1821
+ };
1822
+ refresh();
1823
+ const timer = setInterval(refresh, 5e3);
1824
+ return () => {
1825
+ alive = false;
1826
+ clearInterval(timer);
1827
+ };
1828
+ }, [rpc]);
1829
+ const layers = info?.layerChains;
1830
+ const dotOf = (k) => layers?.[k]?.source ?? "global";
1831
+ const users = ["l1", "l2", "l3"].filter((k) => dotOf(k) === "global");
1832
+ const segOptions = [
1833
+ { key: "g", label: "全局默认", title: "未单独配置的层走这条链(当前在用:" + (users.length ? users.map((k) => LAYER_META[k].seg).join("、") : "无") + ")" },
1834
+ ...["l1", "l2", "l3"].map((k) => ({
1835
+ key: k,
1836
+ title: LAYER_META[k].title + " · " + (dotOf(k) === "runtime" ? "运行时自定义" : dotOf(k) === "static" ? "部署 YAML 层链(只读)" : "跟随全局"),
1837
+ label: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { children: [
1838
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Dot, { kind: dotOf(k) }),
1839
+ LAYER_META[k].seg
1840
+ ] })
1841
+ }))
1842
+ ];
1843
+ const chipTitle = (k) => dotOf(k) === "runtime" ? "本层走设置页自定义链" : dotOf(k) === "static" ? "本层走部署 YAML 层链(UI 只读,自定义可覆盖)" : "本层未单独配置,走全局默认链";
1844
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
1845
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Segmented, { value: tab, options: segOptions, onChange: (k) => setTab(k) }),
1846
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: STY2.hint, children: [
1847
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Dot, { kind: "runtime" }),
1848
+ " 自定义 · ",
1849
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Dot, { kind: "static" }),
1850
+ " 部署 YAML · ",
1851
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Dot, { kind: "global" }),
1852
+ " 跟随全局",
1853
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { title: "每层实际链:运行时自定义 → 部署 YAML 层链 → 全局默认链,逐级兜底;部署 pin 时运行时编辑只读", children: "(层链优先于全局)" })
1854
+ ] }),
1855
+ tab === "g" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
1856
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: STY2.panelHead, children: [
1857
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: STY2.panelTitle, children: "全局默认链" }),
1858
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { ...STY2.chip, ...STY2.chipAccent }, children: "运行时 · 可编辑" }),
1859
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { ...STY2.inUse, marginLeft: "auto" }, children: users.length ? "在用:" + users.map((k) => LAYER_META[k].seg).join("、") : "当前无层使用" })
1860
+ ] }),
1861
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(RouteChainEditor, { rpc, disabled }, "g"),
1862
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(BudgetInputs, { rpc, disabled, data: props.data, setData: props.setData, onError: props.onError, scope: "input" }, "g-budget")
1863
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
1864
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: STY2.panelHead, children: [
1865
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: STY2.panelTitle, children: LAYER_META[tab].title }),
1866
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { ...STY2.chip, ...dotOf(tab) === "runtime" ? STY2.chipAccent : STY2.chipMuted }, title: chipTitle(tab), children: dotOf(tab) === "runtime" ? "运行时自定义" : dotOf(tab) === "static" ? "静态 · YAML" : "跟随全局" })
1867
+ ] }),
1868
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(RouteChainEditor, { rpc, disabled, scope: tab }, tab),
1869
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(BudgetInputs, { rpc, disabled, data: props.data, setData: props.setData, onError: props.onError, scope: tab }, tab + "-budget")
1870
+ ] })
1871
+ ] });
1872
+ }
1873
+
1874
+ // client/src/tabs/EmbeddingSection.tsx
1875
+ var import_react8 = require("react");
1876
+
1877
+ // client/src/rpc.ts
1878
+ function makeRpc(ctx) {
1879
+ return (endpoint, payload) => {
1880
+ if (!ctx.connection || !ctx.connection.rpc) return Promise.reject(new Error("connection 服务不可用"));
1881
+ return ctx.connection.rpc.call("/rpc", endpoint, payload ?? {});
1882
+ };
1883
+ }
1884
+ function asLoose(rpc) {
1885
+ return rpc;
1886
+ }
1887
+
1888
+ // client/src/tabs/EmbeddingSection.tsx
1889
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1890
+ function EmbeddingSection(props) {
1891
+ const rpc = props.rpc;
1892
+ const loose = asLoose(rpc);
1893
+ const [st, setSt] = (0, import_react8.useState)(null);
1894
+ const [err, setErr] = (0, import_react8.useState)(null);
1895
+ const busyPollRef = (0, import_react8.useRef)(null);
1896
+ const load = (0, import_react8.useCallback)(() => {
1897
+ rpc("dsh-memory/embedding-state-get", {}).then((r) => {
1898
+ if (r && r.ok && r.value && r.value.supported !== false) {
1899
+ const v = r.value;
1900
+ if (busyPollRef.current) {
1901
+ busyPollRef.current.v = !!(v.download && (v.download.phase === "downloading" || v.download.phase === "verifying") || v.apply.busy || v.reindex && v.reindex.running || v.runtime && v.runtime.phase === "installing");
1902
+ }
1903
+ setSt(v);
1904
+ setErr(null);
1905
+ } else if (r && r.ok && r.value && r.value.supported === false) {
1906
+ setSt(null);
1907
+ setErr("__unsupported__");
1908
+ } else {
1909
+ setErr(r && !r.ok ? r.error.message : "RPC error");
1910
+ }
1911
+ }).catch((e) => {
1912
+ setErr(String(e && e.message || e));
1913
+ });
1914
+ }, [rpc]);
1915
+ (0, import_react8.useEffect)(() => {
1916
+ load();
1917
+ }, [load]);
1918
+ (0, import_react8.useEffect)(() => {
1919
+ let stopped = false;
1920
+ const busyFlag = { v: false };
1921
+ busyPollRef.current = busyFlag;
1922
+ const tick = () => {
1923
+ if (stopped) return;
1924
+ load();
1925
+ timer = setTimeout(tick, busyFlag.v ? 1200 : 5e3);
1926
+ };
1927
+ let timer = setTimeout(tick, 1200);
1928
+ return () => {
1929
+ stopped = true;
1930
+ clearTimeout(timer);
1931
+ busyPollRef.current = null;
1932
+ };
1933
+ }, [load]);
1934
+ const call = (endpoint, payload, confirmText) => {
1935
+ if (confirmText && !window.confirm(confirmText)) return;
1936
+ loose(endpoint, payload).then((r) => {
1937
+ if (!r || !r.ok) setErr(r && r.error ? r.error.message : "操作失败");
1938
+ else {
1939
+ setErr(null);
1940
+ load();
1941
+ }
1942
+ }).catch((e) => {
1943
+ setErr(String(e && e.message || e));
1944
+ });
1945
+ };
1946
+ if (err === "__unsupported__") {
1947
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dsh-mem-rb-card", children: [
1948
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontWeight: 600, marginBottom: 4 }, children: "语义检索(嵌入)" }),
1949
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "dsh-mem-rb-muted", children: "存储处于降级状态,嵌入管理不可用。" })
1950
+ ] });
1951
+ }
1952
+ if (!st) {
1953
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dsh-mem-rb-card", children: [
1954
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "dsh-mem-rb-muted", children: err ? "嵌入状态读取失败:" + err : "嵌入状态读取中…" }),
1955
+ err ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(NButton, { style: { marginTop: 8 }, onClick: load, children: "重试" }) : null
1956
+ ] });
1957
+ }
1958
+ const switchConfirm = "切换嵌入源后将按新模型重建向量索引(期间语义检索暂退化为关键词匹配,不影响对话)。确定切换?";
1959
+ const onSource = (key) => {
1960
+ if (key === "local") {
1961
+ const ready = [];
1962
+ for (let i = 0; i < st.models.length; i++) if (st.models[i].state === "downloaded") ready.push(st.models[i].id);
1963
+ if (ready.length === 0) {
1964
+ setErr("请先在下方下载一个本地嵌入模型,再切换到本地档。");
1965
+ return;
1966
+ }
1967
+ const current = st.activeModel && ready.indexOf(st.activeModel) >= 0 ? st.activeModel : ready[0];
1968
+ call("dsh-memory/embedding-source-set", { source: "local", activeModel: current }, switchConfirm);
1969
+ return;
1970
+ }
1971
+ call("dsh-memory/embedding-source-set", { source: key }, key === "remote" ? switchConfirm : null);
1972
+ };
1973
+ const dl = st.download;
1974
+ const dlActive = dl && (dl.phase === "downloading" || dl.phase === "verifying");
1975
+ const ap = st.apply;
1976
+ const localInfo = st.local;
1977
+ const rt = st.runtime;
1978
+ let runtimeRow = null;
1979
+ if (rt.phase === "installing") {
1980
+ runtimeRow = /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { marginTop: 8, fontSize: 12 }, children: [
1981
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: S.flexRow, children: [
1982
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "安装推理运行时中… 已耗时 " + Math.round(rt.elapsedMs / 1e3) + "s(约 100~200MB,视网络)" }),
1983
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: S.grow }),
1984
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(NButton, { onClick: () => call("dsh-memory/embedding-runtime-cancel", {}), children: "取消" })
1985
+ ] }),
1986
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("pre", { style: { ...S.pre, maxHeight: 68, marginTop: 6, fontSize: 11, opacity: 0.85 }, children: (rt.lastLines || []).join("\n") || "等待 npm 输出…" })
1987
+ ] });
1988
+ } else if (rt.phase === "error") {
1989
+ runtimeRow = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { marginTop: 8, fontSize: 12, color: "var(--dsh-mem-danger)" }, children: "运行时安装失败:" + (rt.error || "未知") + "(重新切换嵌入源可重试)" });
1990
+ } else if (rt.phase === "ready") {
1991
+ runtimeRow = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "dsh-mem-rb-muted", style: { marginTop: 8 }, children: "推理运行时就绪(transformers.js v" + rt.installedVersion + ")" });
1992
+ } else if (st.source === "local") {
1993
+ runtimeRow = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "dsh-mem-rb-muted", style: { marginTop: 8 }, children: "首次启用本地嵌入时会自动安装推理运行时(约 100~200MB)。" });
1994
+ }
1995
+ let applyRow = null;
1996
+ if (ap.phase === "warming") {
1997
+ applyRow = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "dsh-mem-rb-muted", style: { marginTop: 8 }, children: "加载嵌入模型中…(首次需数秒)" });
1998
+ } else if (ap.phase === "switching") {
1999
+ applyRow = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "dsh-mem-rb-muted", style: { marginTop: 8 }, children: "切换嵌入源中…" });
2000
+ } else if (ap.phase === "error") {
2001
+ applyRow = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { marginTop: 8, fontSize: 12, color: "var(--dsh-mem-danger)" }, children: "切换失败:" + ap.message + "(已保存的嵌入源不变,重启后仍按原源运行)" });
2002
+ } else if (st.reindex && st.reindex.running) {
2003
+ const rj = st.reindex;
2004
+ const rDone = rj.l1Done + rj.l0Done;
2005
+ const rTotal = rj.l1Total + rj.l0Total;
2006
+ const rPct = rTotal > 0 ? Math.round(rDone / rTotal * 100) : 0;
2007
+ applyRow = /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { marginTop: 8 }, children: [
2008
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: S.flexRow, children: [
2009
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dsh-mem-rb-muted", children: "重嵌入中 L1 " + rj.l1Done + "/" + rj.l1Total + " · L0 " + rj.l0Done + "/" + rj.l0Total + "(" + rPct + "%)" }),
2010
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: S.grow }),
2011
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(NButton, { onClick: () => call("dsh-memory/embedding-reindex-cancel", {}), children: "取消" })
2012
+ ] }),
2013
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { ...S.flexRow, marginTop: 6 }, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "dsh-mem-rb-bar", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "dsh-mem-rb-fill", style: { width: rPct + "%" } }) }) })
2014
+ ] });
2015
+ }
2016
+ const modelCards = st.models.map((m) => {
2017
+ const isActive = st.source === "local" && st.activeModel === m.id;
2018
+ const mDl = dlActive && dl.modelId === m.id;
2019
+ const pct = mDl && dl.overallTotal > 0 ? Math.round(dl.overallReceived / dl.overallTotal * 100) : 0;
2020
+ let action = null;
2021
+ if (mDl) {
2022
+ action = /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { flex: 1, minWidth: 200 }, children: [
2023
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: S.flexRow, children: [
2024
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dsh-mem-rb-muted", style: { whiteSpace: "nowrap" }, children: (dl.phase === "verifying" ? "校验中 " : "") + fmtMB(dl.overallReceived) + " / " + fmtMB(dl.overallTotal) + "(文件 " + dl.fileIndex + "/" + dl.fileCount + "," + pct + "%" + (dl.speedBps > 0 && dl.phase === "downloading" ? "," + fmtMB(dl.speedBps) + "/s" : "") + ")" }),
2025
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: S.grow }),
2026
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(NButton, { onClick: () => call("dsh-memory/embedding-download-cancel", {}), children: "取消" })
2027
+ ] }),
2028
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { ...S.flexRow, marginTop: 6 }, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "dsh-mem-rb-bar", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "dsh-mem-rb-fill", style: { width: pct + "%" } }) }) })
2029
+ ] });
2030
+ } else if (isActive) {
2031
+ action = /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: S.flexRow, children: [
2032
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dsh-mem-tag dsh-mem-tag-work-task", children: "使用中" }),
2033
+ localInfo && localInfo.state === "loading" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dsh-mem-rb-muted", children: "模型加载中…" }) : null,
2034
+ localInfo && localInfo.state === "failed" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { fontSize: 12, color: "var(--dsh-mem-danger)" }, children: "加载失败:" + (localInfo.error || "") }) : null,
2035
+ localInfo && localInfo.state === "ready" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dsh-mem-rb-muted", children: "已就绪" }) : null
2036
+ ] });
2037
+ } else if (m.state === "downloaded") {
2038
+ action = /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: S.flexRow, children: [
2039
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2040
+ NButton,
2041
+ {
2042
+ disabled: ap.busy,
2043
+ onClick: () => call("dsh-memory/embedding-source-set", { source: "local", activeModel: m.id }, switchConfirm),
2044
+ children: "启用"
2045
+ }
2046
+ ),
2047
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2048
+ NButton,
2049
+ {
2050
+ disabled: dlActive,
2051
+ onClick: () => call("dsh-memory/embedding-model-delete", { modelId: m.id }, "删除已下载的 " + m.name + "(" + fmtMB(m.totalBytes) + ")?"),
2052
+ children: "删除"
2053
+ }
2054
+ )
2055
+ ] });
2056
+ } else {
2057
+ action = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2058
+ NButton,
2059
+ {
2060
+ disabled: dlActive || !st.ceilings.local,
2061
+ title: !st.ceilings.local ? "部署已禁用本地嵌入模型" : "",
2062
+ onClick: () => call("dsh-memory/embedding-download-start", { modelId: m.id }),
2063
+ children: (m.state === "partial" ? "继续下载 " : "下载 ") + fmtMB(m.totalBytes)
2064
+ }
2065
+ );
2066
+ }
2067
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2068
+ "div",
2069
+ {
2070
+ style: { ...S.flexRow, padding: "8px 0", borderBottom: "1px solid var(--dsh-mem-border)", flexWrap: "wrap" },
2071
+ children: [
2072
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { minWidth: 150 }, children: [
2073
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontWeight: 600 }, children: m.name }),
2074
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "dsh-mem-rb-muted", children: m.tags.join(" · ") + " · " + m.dims + " 维 · 上下文 " + m.contextTokens })
2075
+ ] }),
2076
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { flex: 1, minWidth: 180, fontSize: 12, color: "var(--dsh-mem-text-2)" }, children: m.description }),
2077
+ action
2078
+ ]
2079
+ },
2080
+ m.id
2081
+ );
2082
+ });
2083
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dsh-mem-rb-card", children: [
2084
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: S.flexRow, children: [
2085
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontWeight: 600, whiteSpace: "nowrap" }, children: "语义检索(嵌入源)" }),
2014
2086
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: S.grow }),
2015
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(NButton, { variant: "primary", disabled, onClick: save, children: "保存" })
2016
- ] }),
2017
- err ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { ...STY.warn, marginTop: 8 }, children: "✕ " + err }) : null,
2018
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { marginTop: 10, paddingTop: 10, borderTop: "1px solid var(--dsh-mem-border)" }, children: [
2019
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontSize: 11, color: "var(--dsh-mem-text-3)", marginBottom: 4 }, children: "实际链" + (dirty ? "(保存后更新;当前显示已保存值)" : "") }),
2020
2087
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2021
- "div",
2088
+ Segmented,
2022
2089
  {
2023
- style: { fontSize: 12, color: "var(--dsh-mem-text-2)", wordBreak: "break-all", fontFamily: "ui-monospace, SFMono-Regular, Consolas, monospace" },
2024
- children: effChain.map((e) => e.provider + "/" + e.model + (e.effort ? "(" + e.effort + ")" : "")).join(" → ") || "(暂无可用路由)"
2090
+ value: st.source,
2091
+ options: [
2092
+ { key: "off", label: "关闭" },
2093
+ { key: "local", label: "本地", disabled: !st.ceilings.local, disabledTitle: "部署已禁用本地嵌入模型" },
2094
+ { key: "remote", label: "远程", disabled: !st.ceilings.remote, disabledTitle: "部署未配置远程嵌入(baseUrl/apiKey/model/dimensions)" }
2095
+ ],
2096
+ onChange: onSource
2025
2097
  }
2026
2098
  )
2027
- ] })
2099
+ ] }),
2100
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "dsh-mem-rb-muted", style: { marginTop: 4 }, children: st.source === "off" ? "当前:关键词(BM25)检索,不做向量嵌入" : st.source === "remote" ? "当前:远程嵌入(" + (rt ? "" : "") + "模型由部署配置给定)" : "当前:本地嵌入" + (st.activeModel ? "(" + st.activeModel + ")" : "") }),
2101
+ st.activeNote ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { marginTop: 4, fontSize: 12, color: "var(--dsh-mem-danger)" }, children: st.activeNote }) : null,
2102
+ err && err !== "__unsupported__" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { marginTop: 6, fontSize: 12, color: "var(--dsh-mem-danger)" }, children: err }) : null,
2103
+ dl && dl.phase === "error" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { marginTop: 6, fontSize: 12, color: "var(--dsh-mem-danger)" }, children: "下载失败:" + (dl.error || "") }) : null,
2104
+ runtimeRow,
2105
+ applyRow,
2106
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: S.panelLabel, children: "本地模型目录(下载后离线可用,不随插件分发)" }),
2107
+ modelCards
2028
2108
  ] });
2029
2109
  }
2030
2110
 
2031
- // client/src/tabs/OverviewTab.tsx
2111
+ // client/src/tabs/RebuildPanel.tsx
2112
+ var import_react9 = require("react");
2032
2113
  var import_jsx_runtime9 = require("react/jsx-runtime");
2114
+ var RB_PHASE_LABEL = {
2115
+ preparing: "准备中(归档旧数据 · 清空检索库)",
2116
+ distilling: "分块蒸馏中",
2117
+ finalizing: "收尾(强制 L2 场景 + L3 画像)"
2118
+ };
2119
+ function RebuildPanel(props) {
2120
+ const rpc = props.rpc;
2121
+ const [rbRaw, setRb] = (0, import_react9.useState)(null);
2122
+ const [confirmOpen, setConfirmOpen] = (0, import_react9.useState)(false);
2123
+ const [busy, setBusy] = (0, import_react9.useState)(false);
2124
+ const [rbError, setRbError] = (0, import_react9.useState)(null);
2125
+ const refresh = (0, import_react9.useCallback)(() => {
2126
+ rpc("dsh-memory/rebuild-status", {}).then((r) => {
2127
+ if (r && r.ok) setRb(r.value);
2128
+ }).catch(() => {
2129
+ });
2130
+ }, [rpc]);
2131
+ (0, import_react9.useEffect)(() => {
2132
+ refresh();
2133
+ }, [refresh]);
2134
+ const running = !!(rbRaw && rbRaw.running);
2135
+ (0, import_react9.useEffect)(() => {
2136
+ if (!running) return;
2137
+ const timer = setInterval(refresh, 1500);
2138
+ return () => {
2139
+ clearInterval(timer);
2140
+ };
2141
+ }, [running, refresh]);
2142
+ if (!rbRaw || rbRaw.supported === false) return null;
2143
+ const rb = rbRaw;
2144
+ ensureThemeStyle();
2145
+ const start = () => {
2146
+ setBusy(true);
2147
+ setRbError(null);
2148
+ rpc("dsh-memory/rebuild-start", {}).then((r) => {
2149
+ setBusy(false);
2150
+ if (r && r.ok) {
2151
+ setConfirmOpen(false);
2152
+ setRb(r.value);
2153
+ } else {
2154
+ setRbError(r && r.error ? r.error.message : "启动失败");
2155
+ }
2156
+ }).catch((e) => {
2157
+ setBusy(false);
2158
+ setRbError(String(e && e.message || e));
2159
+ });
2160
+ };
2161
+ const cancel = () => {
2162
+ setBusy(true);
2163
+ rpc("dsh-memory/rebuild-cancel", {}).then((r) => {
2164
+ setBusy(false);
2165
+ if (r && r.ok) setRb(r.value);
2166
+ }).catch(() => {
2167
+ setBusy(false);
2168
+ });
2169
+ };
2170
+ const empty = !running && (rb.messageCount === 0 || rb.estCalls === 0);
2171
+ const pct = rb.total > 0 ? Math.round(rb.done / rb.total * 100) : 0;
2172
+ let lastNote = null;
2173
+ if (!running && rb.phase === "done") {
2174
+ lastNote = "上次重建:完成(" + rb.done + "/" + rb.total + " 会话,产出 " + rb.recordsBuilt + " 条记录)" + (rb.finishedAt ? " · " + fmtTime(new Date(rb.finishedAt).toISOString()) : "");
2175
+ } else if (!running && rb.phase === "cancelled") {
2176
+ lastNote = "上次重建:已取消(完成 " + rb.done + "/" + rb.total + " 会话,已重建部分保留)";
2177
+ } else if (!running && rb.phase === "failed") {
2178
+ lastNote = "上次重建:失败:" + (rb.error || "未知错误");
2179
+ }
2180
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "dsh-mem-rb-card", children: [
2181
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }, children: [
2182
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { fontWeight: 600, whiteSpace: "nowrap" }, children: "重建记忆" }),
2183
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "dsh-mem-rb-muted", style: { flex: 1, minWidth: 180 }, children: running ? (RB_PHASE_LABEL[rb.phase] || rb.phase) + " · " + rb.done + "/" + rb.total + " 会话(" + pct + "%)" : "从 L0 原始对话重新蒸馏 L1/L2/L3;旧数据先归档(不删除)" }),
2184
+ running ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(NButton, { disabled: busy || rb.cancelRequested, onClick: cancel, children: rb.cancelRequested ? "取消中…" : "取消重建" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2185
+ NButton,
2186
+ {
2187
+ disabled: busy || empty,
2188
+ title: empty ? "L0 无消息,无可重建内容" : "重新蒸馏全部记忆",
2189
+ style: { color: "var(--dsh-mem-danger)" },
2190
+ onClick: () => {
2191
+ setConfirmOpen(true);
2192
+ },
2193
+ children: busy ? "…" : "开始重建"
2194
+ }
2195
+ )
2196
+ ] }),
2197
+ running ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 10, marginTop: 10 }, children: [
2198
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "dsh-mem-rb-bar", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "dsh-mem-rb-fill", style: { width: pct + "%" } }) }),
2199
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "dsh-mem-rb-muted", style: { whiteSpace: "nowrap" }, children: "产出 " + rb.recordsBuilt + " 条" })
2200
+ ] }) : null,
2201
+ lastNote ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "dsh-mem-rb-muted", style: { marginTop: 8 }, children: lastNote }) : null,
2202
+ rbError ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { marginTop: 8, fontSize: 12, color: "var(--dsh-mem-danger)" }, children: rbError }) : null,
2203
+ confirmOpen ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2204
+ NModal,
2205
+ {
2206
+ open: true,
2207
+ onClose: () => {
2208
+ setConfirmOpen(false);
2209
+ },
2210
+ title: "确认重建全部记忆?",
2211
+ footer: [
2212
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2213
+ NButton,
2214
+ {
2215
+ onClick: () => {
2216
+ setConfirmOpen(false);
2217
+ },
2218
+ children: "取消"
2219
+ },
2220
+ "cancel"
2221
+ ),
2222
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(NButton, { variant: "primary", disabled: busy, onClick: start, children: busy ? "启动中…" : "开始重建" }, "confirm")
2223
+ ],
2224
+ children: [
2225
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
2226
+ "将以 L0 原始对话为事实源重新蒸馏:",
2227
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("b", { children: rb.sessionCount + " 个会话 · " + rb.messageCount + " 条消息" }),
2228
+ ",预计 ≥" + rb.estCalls + " 次蒸馏调用。"
2229
+ ] }),
2230
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { marginTop: 8 }, children: "现有 L1 记忆 / L2 场景 / L3 画像会整体归档(*.bak.时间戳,可手工找回),随后清空重建;重建期间可正常对话,新对话的蒸馏优先进行;中途可取消,已重建部分保留。" })
2231
+ ]
2232
+ }
2233
+ ) : null
2234
+ ] });
2235
+ }
2236
+
2237
+ // client/src/tabs/OverviewTab.tsx
2238
+ var import_jsx_runtime10 = require("react/jsx-runtime");
2033
2239
  function OverviewTab(props) {
2034
2240
  const rpc = props.rpc;
2035
- const [stats, setStats] = (0, import_react9.useState)(null);
2036
- const [settingsData, setSettingsData] = (0, import_react9.useState)(null);
2037
- const [error, setError] = (0, import_react9.useState)(null);
2038
- const load = (0, import_react9.useCallback)(() => {
2241
+ const [stats, setStats] = (0, import_react10.useState)(null);
2242
+ const [settingsData, setSettingsData] = (0, import_react10.useState)(null);
2243
+ const [error, setError] = (0, import_react10.useState)(null);
2244
+ const load = (0, import_react10.useCallback)(() => {
2039
2245
  rpc("dsh-memory/stats", {}).then((r) => {
2040
2246
  if (r && r.ok) setStats(r.value);
2041
2247
  else setError(r && r.error ? r.error.message : "RPC error");
@@ -2047,7 +2253,7 @@ var __defProp = Object.defineProperty;
2047
2253
  }).catch(() => {
2048
2254
  });
2049
2255
  }, [rpc]);
2050
- (0, import_react9.useEffect)(() => {
2256
+ (0, import_react10.useEffect)(() => {
2051
2257
  load();
2052
2258
  const timer = setInterval(load, 5e3);
2053
2259
  return () => {
@@ -2058,7 +2264,7 @@ var __defProp = Object.defineProperty;
2058
2264
  if (!settingsData) return;
2059
2265
  const prev = settingsData;
2060
2266
  const patch = { [key]: value };
2061
- const next = { ...prev, settings: { ...prev.settings, ...patch } };
2267
+ const next = { ...prev, settings: { ...prev.settings, [key]: value } };
2062
2268
  setSettingsData(next);
2063
2269
  rpc("dsh-memory/settings-set", patch).then((r) => {
2064
2270
  if (!r || !r.ok) {
@@ -2101,10 +2307,10 @@ var __defProp = Object.defineProperty;
2101
2307
  if (!settingsData.ceilings.recall) off.push("召回");
2102
2308
  if (off.length > 0) ceilingNote = "注意:部署配置已停用 " + off.join("、") + "(运行时开关无法开启)";
2103
2309
  }
2104
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
2105
- settingsData && settingsData.supported === false ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: S.hint, children: "settings 服务不可用,记忆模式开关未启用(记忆保持全开)。" }) : settingsData ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: S.switchPanel, children: [
2106
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: S.panelLabel, children: "记忆模式" }),
2107
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2310
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
2311
+ settingsData && settingsData.supported === false ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: S.hint, children: "settings 服务不可用,记忆模式开关未启用(记忆保持全开)。" }) : settingsData ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: S.switchPanel, children: [
2312
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: S.panelLabel, children: "记忆模式" }),
2313
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2108
2314
  SwitchRow,
2109
2315
  {
2110
2316
  label: "记忆模式",
@@ -2115,7 +2321,7 @@ var __defProp = Object.defineProperty;
2115
2321
  }
2116
2322
  }
2117
2323
  ),
2118
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2324
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2119
2325
  SwitchRow,
2120
2326
  {
2121
2327
  label: "捕获",
@@ -2127,7 +2333,7 @@ var __defProp = Object.defineProperty;
2127
2333
  }
2128
2334
  }
2129
2335
  ),
2130
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2336
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2131
2337
  SwitchRow,
2132
2338
  {
2133
2339
  label: "蒸馏",
@@ -2139,7 +2345,7 @@ var __defProp = Object.defineProperty;
2139
2345
  }
2140
2346
  }
2141
2347
  ),
2142
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2348
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2143
2349
  SwitchRow,
2144
2350
  {
2145
2351
  label: "召回",
@@ -2151,42 +2357,41 @@ var __defProp = Object.defineProperty;
2151
2357
  }
2152
2358
  }
2153
2359
  ),
2154
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: S.panelLabel, children: "蒸馏参数" }),
2155
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(RouteChainEditor, { rpc, disabled: !master }),
2156
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(BudgetInputs, { rpc, disabled: !master, data: settingsData, setData: setSettingsData, onError: setError }),
2157
- ceilingNote ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: S.hint, children: ceilingNote }) : null
2360
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: S.panelLabel, children: "蒸馏参数" }),
2361
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DistillSettings, { rpc, disabled: !master, data: settingsData, setData: setSettingsData, onError: setError }),
2362
+ ceilingNote ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: S.hint, children: ceilingNote }) : null
2158
2363
  ] }) : null,
2159
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(EmbeddingSection, { rpc }),
2160
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(RebuildPanel, { rpc }),
2161
- degraded ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { ...S.error, marginBottom: 10 }, children: "⚠ " + stats.message + "。上方数据为最后一次成功读取的值,记忆功能当前未工作。" }) : null,
2162
- error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: S.error, children: "获取状态失败:" + error }) : !stats ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: S.intro, children: "正在读取记忆状态…" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
2163
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: S.panelLabel, children: "记忆概况" }),
2164
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: S.statGrid, children: tiles.map((t) => {
2165
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "dsh-mem-card", style: S.statTile, children: [
2166
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: S.statNum, children: t.num }),
2167
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: S.statLabel, children: t.label })
2364
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(EmbeddingSection, { rpc }),
2365
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(RebuildPanel, { rpc }),
2366
+ degraded ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { ...S.error, marginBottom: 10 }, children: "⚠ " + stats.message + "。上方数据为最后一次成功读取的值,记忆功能当前未工作。" }) : null,
2367
+ error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: S.error, children: "获取状态失败:" + error }) : !stats ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: S.intro, children: "正在读取记忆状态…" }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
2368
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: S.panelLabel, children: "记忆概况" }),
2369
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: S.statGrid, children: tiles.map((t) => {
2370
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "dsh-mem-card", style: S.statTile, children: [
2371
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: S.statNum, children: t.num }),
2372
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: S.statLabel, children: t.label })
2168
2373
  ] }, t.label);
2169
2374
  }) }),
2170
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: S.panelLabel, children: "运行状态" }),
2171
- infos.map((row) => {
2172
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: S.infoRow, children: [
2173
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: S.infoKey, children: row[0] }),
2174
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: S.infoVal, children: row[1] })
2175
- ] }, row[0]);
2375
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: S.panelLabel, children: "运行状态" }),
2376
+ infos.map((row2) => {
2377
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: S.infoRow, children: [
2378
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: S.infoKey, children: row2[0] }),
2379
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: S.infoVal, children: row2[1] })
2380
+ ] }, row2[0]);
2176
2381
  })
2177
2382
  ] }),
2178
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: S.hint, children: "浏览各层记忆内容请切换上方 Tab;原始对话(L0)不入浏览器,可由模型侧 conversation_search 工具查询。" })
2383
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: S.hint, children: "浏览各层记忆内容请切换上方 Tab;原始对话(L0)不入浏览器,可由模型侧 conversation_search 工具查询。" })
2179
2384
  ] });
2180
2385
  }
2181
2386
 
2182
2387
  // client/src/tabs/PersonaTab.tsx
2183
- var import_react10 = require("react");
2184
- var import_jsx_runtime10 = require("react/jsx-runtime");
2388
+ var import_react11 = require("react");
2389
+ var import_jsx_runtime11 = require("react/jsx-runtime");
2185
2390
  function PersonaTab(props) {
2186
2391
  const rpc = props.rpc;
2187
- const [content, setContent] = (0, import_react10.useState)(null);
2188
- const [error, setError] = (0, import_react10.useState)(null);
2189
- const load = (0, import_react10.useCallback)(() => {
2392
+ const [content, setContent] = (0, import_react11.useState)(null);
2393
+ const [error, setError] = (0, import_react11.useState)(null);
2394
+ const load = (0, import_react11.useCallback)(() => {
2190
2395
  setError(null);
2191
2396
  rpc("dsh-memory/persona", {}).then((r) => {
2192
2397
  if (r && r.ok) setContent(r.value.content);
@@ -2195,23 +2400,23 @@ var __defProp = Object.defineProperty;
2195
2400
  setError(String(e && e.message || e));
2196
2401
  });
2197
2402
  }, [rpc]);
2198
- (0, import_react10.useEffect)(() => {
2403
+ (0, import_react11.useEffect)(() => {
2199
2404
  load();
2200
2405
  }, [load]);
2201
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
2202
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { ...S.flexRow, marginBottom: 10 }, children: [
2203
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: S.muted, children: error ? "加载失败" : content === null ? "加载中…" : content ? content.length + " 字符" : "未生成画像" }),
2204
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: S.grow }),
2205
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(NButton, { onClick: load, children: "刷新" })
2406
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
2407
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { ...S.flexRow, marginBottom: 10 }, children: [
2408
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: S.muted, children: error ? "加载失败" : content === null ? "加载中…" : content ? content.length + " 字符" : "未生成画像" }),
2409
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: S.grow }),
2410
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(NButton, { onClick: load, children: "刷新" })
2206
2411
  ] }),
2207
- error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { ...S.error, marginBottom: 10 }, children: "画像读取失败:" + error + "(点右上“刷新”重试)" }) : null,
2208
- content ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("pre", { style: S.pre, children: content }) : content === null ? null : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: S.intro, children: "画像尚未生成;蒸馏若干记忆后 L3 会自动产出。" })
2412
+ error ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { ...S.error, marginBottom: 10 }, children: "画像读取失败:" + error + "(点右上“刷新”重试)" }) : null,
2413
+ content ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("pre", { style: S.pre, children: content }) : content === null ? null : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: S.intro, children: "画像尚未生成;蒸馏若干记忆后 L3 会自动产出。" })
2209
2414
  ] });
2210
2415
  }
2211
2416
 
2212
2417
  // client/src/tabs/RecordsTab.tsx
2213
- var import_react11 = require("react");
2214
- var import_jsx_runtime11 = require("react/jsx-runtime");
2418
+ var import_react12 = require("react");
2419
+ var import_jsx_runtime12 = require("react/jsx-runtime");
2215
2420
  var TYPE_CHOICES = [
2216
2421
  "persona",
2217
2422
  "episodic",
@@ -2224,20 +2429,20 @@ var __defProp = Object.defineProperty;
2224
2429
  function RecordsTab(props) {
2225
2430
  const rpc = props.rpc;
2226
2431
  const limit = 50;
2227
- const [items, setItems] = (0, import_react11.useState)([]);
2228
- const [hasMore, setHasMore] = (0, import_react11.useState)(false);
2229
- const [total, setTotal] = (0, import_react11.useState)(null);
2230
- const [sceneOptions, setSceneOptions] = (0, import_react11.useState)([]);
2231
- const [loading, setLoading] = (0, import_react11.useState)(false);
2232
- const [truncated, setTruncated] = (0, import_react11.useState)(false);
2233
- const [error, setError] = (0, import_react11.useState)(null);
2234
- const [expandedId, setExpandedId] = (0, import_react11.useState)(null);
2235
- const [query, setQuery] = (0, import_react11.useState)("");
2236
- const [typeFilter, setTypeFilter] = (0, import_react11.useState)("");
2237
- const [sceneFilter, setSceneFilter] = (0, import_react11.useState)("");
2238
- const [last, setLast] = (0, import_react11.useState)({ query: "", type: "", scene: "" });
2239
- const seqRef = (0, import_react11.useRef)(0);
2240
- const fetchPage = (0, import_react11.useCallback)(
2432
+ const [items, setItems] = (0, import_react12.useState)([]);
2433
+ const [hasMore, setHasMore] = (0, import_react12.useState)(false);
2434
+ const [total, setTotal] = (0, import_react12.useState)(null);
2435
+ const [sceneOptions, setSceneOptions] = (0, import_react12.useState)([]);
2436
+ const [loading, setLoading] = (0, import_react12.useState)(false);
2437
+ const [truncated, setTruncated] = (0, import_react12.useState)(false);
2438
+ const [error, setError] = (0, import_react12.useState)(null);
2439
+ const [expandedId, setExpandedId] = (0, import_react12.useState)(null);
2440
+ const [query, setQuery] = (0, import_react12.useState)("");
2441
+ const [typeFilter, setTypeFilter] = (0, import_react12.useState)("");
2442
+ const [sceneFilter, setSceneFilter] = (0, import_react12.useState)("");
2443
+ const [last, setLast] = (0, import_react12.useState)({ query: "", type: "", scene: "" });
2444
+ const seqRef = (0, import_react12.useRef)(0);
2445
+ const fetchPage = (0, import_react12.useCallback)(
2241
2446
  (conds, offset, append) => {
2242
2447
  setLoading(true);
2243
2448
  setError(null);
@@ -2272,13 +2477,13 @@ var __defProp = Object.defineProperty;
2272
2477
  setLast(conds);
2273
2478
  fetchPage(conds, 0, false);
2274
2479
  };
2275
- (0, import_react11.useEffect)(() => {
2480
+ (0, import_react12.useEffect)(() => {
2276
2481
  fetchPage({ query: "", type: "", scene: "" }, 0, false);
2277
2482
  }, [fetchPage]);
2278
2483
  const countText = total !== null ? "共 " + total + " 条" : items.length + " 条" + (hasMore ? "+" : "");
2279
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
2280
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: S.toolbar, children: [
2281
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2484
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { children: [
2485
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: S.toolbar, children: [
2486
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2282
2487
  NInput,
2283
2488
  {
2284
2489
  style: { flex: 1, minWidth: 160 },
@@ -2292,7 +2497,7 @@ var __defProp = Object.defineProperty;
2292
2497
  }
2293
2498
  }
2294
2499
  ),
2295
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2500
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2296
2501
  NSel,
2297
2502
  {
2298
2503
  style: { maxWidth: 200 },
@@ -2305,7 +2510,7 @@ var __defProp = Object.defineProperty;
2305
2510
  onChange: setTypeFilter
2306
2511
  }
2307
2512
  ),
2308
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2513
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2309
2514
  NSel,
2310
2515
  {
2311
2516
  style: { maxWidth: 220 },
@@ -2318,12 +2523,12 @@ var __defProp = Object.defineProperty;
2318
2523
  onChange: setSceneFilter
2319
2524
  }
2320
2525
  ),
2321
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(NButton, { onClick: search, children: "搜索" })
2526
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(NButton, { onClick: search, children: "搜索" })
2322
2527
  ] }),
2323
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { ...S.flexRow, marginBottom: 10 }, children: [
2324
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: S.muted, children: loading ? "加载中…" : countText }),
2325
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: S.grow }),
2326
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2528
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { ...S.flexRow, marginBottom: 10 }, children: [
2529
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: S.muted, children: loading ? "加载中…" : countText }),
2530
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: S.grow }),
2531
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2327
2532
  NButton,
2328
2533
  {
2329
2534
  onClick: () => {
@@ -2333,11 +2538,11 @@ var __defProp = Object.defineProperty;
2333
2538
  }
2334
2539
  )
2335
2540
  ] }),
2336
- error ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: S.error, children: error }) : null,
2337
- truncated ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: S.hint, children: "搜索分页已达检索上限(200 条),更早的结果未显示。请用更精确的关键词或类型/情境过滤。" }) : null,
2338
- items.length === 0 && !loading && !error ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: S.intro, children: "暂无记忆。对话几轮后,蒸馏管线会自动抽取记忆。" }) : items.map((m) => {
2541
+ error ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: S.error, children: error }) : null,
2542
+ truncated ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: S.hint, children: "搜索分页已达检索上限(200 条),更早的结果未显示。请用更精确的关键词或类型/情境过滤。" }) : null,
2543
+ items.length === 0 && !loading && !error ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: S.intro, children: "暂无记忆。对话几轮后,蒸馏管线会自动抽取记忆。" }) : items.map((m) => {
2339
2544
  const open = expandedId === m.id;
2340
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2545
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2341
2546
  "div",
2342
2547
  {
2343
2548
  className: "dsh-mem-card dsh-mem-card-hover",
@@ -2346,23 +2551,23 @@ var __defProp = Object.defineProperty;
2346
2551
  setExpandedId(open ? null : m.id);
2347
2552
  },
2348
2553
  children: [
2349
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: S.cardHead, children: [
2350
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "dsh-mem-tag dsh-mem-tag-" + m.type, children: TYPE_LABELS[m.type] || m.type }),
2351
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: S.muted, children: "优先级 " + m.priority }),
2352
- m.score !== null && m.score !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: S.muted, children: "相关度 " + Number(m.score).toFixed(2) }) : null,
2353
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: S.grow }),
2354
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: S.muted, children: fmtTime(m.updatedAt) })
2554
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: S.cardHead, children: [
2555
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "dsh-mem-tag dsh-mem-tag-" + m.type, children: TYPE_LABELS[m.type] || m.type }),
2556
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: S.muted, children: "优先级 " + m.priority }),
2557
+ m.score !== null && m.score !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: S.muted, children: "相关度 " + Number(m.score).toFixed(2) }) : null,
2558
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: S.grow }),
2559
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: S.muted, children: fmtTime(m.updatedAt) })
2355
2560
  ] }),
2356
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: S.content, children: m.content }),
2357
- open ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: S.detail, children: "id: " + m.id + "\n情境: " + (m.scene || "-") + "\n版本: v" + m.version + "(去重合并次数 " + m.version + ")\n创建: " + fmtTime(m.createdAt) + "\n活跃时间: " + (m.timestamps && m.timestamps.length > 0 ? m.timestamps.map(fmtTime).join(" → ") : "-") + "\n" + (m.sourceMessageIds && m.sourceMessageIds.length > 0 ? "来源消息: " + m.sourceMessageIds.join(", ") : "来源消息: -") }) : null
2561
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: S.content, children: m.content }),
2562
+ open ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: S.detail, children: "id: " + m.id + "\n情境: " + (m.scene || "-") + "\n版本: v" + m.version + "(去重合并次数 " + m.version + ")\n创建: " + fmtTime(m.createdAt) + "\n活跃时间: " + (m.timestamps && m.timestamps.length > 0 ? m.timestamps.map(fmtTime).join(" → ") : "-") + "\n" + (m.sourceMessageIds && m.sourceMessageIds.length > 0 ? "来源消息: " + m.sourceMessageIds.join(", ") : "来源消息: -") }) : null
2358
2563
  ]
2359
2564
  },
2360
2565
  m.id
2361
2566
  );
2362
2567
  }),
2363
- hasMore ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: S.flexRow, children: [
2364
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: S.grow }),
2365
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2568
+ hasMore ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: S.flexRow, children: [
2569
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: S.grow }),
2570
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2366
2571
  NButton,
2367
2572
  {
2368
2573
  disabled: loading,
@@ -2377,13 +2582,13 @@ var __defProp = Object.defineProperty;
2377
2582
  }
2378
2583
 
2379
2584
  // client/src/tabs/ScenesTab.tsx
2380
- var import_react12 = require("react");
2381
- var import_jsx_runtime12 = require("react/jsx-runtime");
2585
+ var import_react13 = require("react");
2586
+ var import_jsx_runtime13 = require("react/jsx-runtime");
2382
2587
  function SceneCard(props) {
2383
2588
  const s = props.s;
2384
- const [open, setOpen] = (0, import_react12.useState)(false);
2385
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "dsh-mem-card dsh-mem-card-hover", style: S.card, children: [
2386
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2589
+ const [open, setOpen] = (0, import_react13.useState)(false);
2590
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "dsh-mem-card dsh-mem-card-hover", style: S.card, children: [
2591
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2387
2592
  "div",
2388
2593
  {
2389
2594
  style: { ...S.sceneHead, cursor: "pointer", userSelect: "none" },
@@ -2391,23 +2596,23 @@ var __defProp = Object.defineProperty;
2391
2596
  setOpen(!open);
2392
2597
  },
2393
2598
  children: [
2394
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "dsh-mem-scene-chev", style: { transform: open ? "rotate(90deg)" : "none" }, children: "▸" }),
2395
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: S.sceneTitle, children: s.path }),
2396
- s.heat ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: S.muted, children: "热度 " + s.heat }) : null,
2397
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: S.grow }),
2398
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: S.muted, children: "更新 " + fmtTime(s.updated) })
2599
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "dsh-mem-scene-chev", style: { transform: open ? "rotate(90deg)" : "none" }, children: "▸" }),
2600
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: S.sceneTitle, children: s.path }),
2601
+ s.heat ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: S.muted, children: "热度 " + s.heat }) : null,
2602
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: S.grow }),
2603
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: S.muted, children: "更新 " + fmtTime(s.updated) })
2399
2604
  ]
2400
2605
  }
2401
2606
  ),
2402
- s.summary ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: { ...S.muted, marginBottom: 6 }, children: s.summary }) : null,
2403
- open ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("pre", { style: S.pre, children: s.content || "(空)" }) : null
2607
+ s.summary ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: { ...S.muted, marginBottom: 6 }, children: s.summary }) : null,
2608
+ open ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("pre", { style: S.pre, children: s.content || "(空)" }) : null
2404
2609
  ] });
2405
2610
  }
2406
2611
  function ScenesTab(props) {
2407
2612
  const rpc = props.rpc;
2408
- const [items, setItems] = (0, import_react12.useState)(null);
2409
- const [error, setError] = (0, import_react12.useState)(null);
2410
- const load = (0, import_react12.useCallback)(() => {
2613
+ const [items, setItems] = (0, import_react13.useState)(null);
2614
+ const [error, setError] = (0, import_react13.useState)(null);
2615
+ const load = (0, import_react13.useCallback)(() => {
2411
2616
  rpc("dsh-memory/scenes", {}).then((r) => {
2412
2617
  if (r && r.ok) {
2413
2618
  setItems(r.value.items);
@@ -2417,25 +2622,25 @@ var __defProp = Object.defineProperty;
2417
2622
  setError(String(e && e.message || e));
2418
2623
  });
2419
2624
  }, [rpc]);
2420
- (0, import_react12.useEffect)(() => {
2625
+ (0, import_react13.useEffect)(() => {
2421
2626
  load();
2422
2627
  }, [load]);
2423
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { children: [
2424
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { ...S.flexRow, marginBottom: 10 }, children: [
2425
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: S.muted, children: items ? items.length + " 个场景块" : "加载中…" }),
2426
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: S.grow }),
2427
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(NButton, { onClick: load, children: "刷新" })
2628
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
2629
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { style: { ...S.flexRow, marginBottom: 10 }, children: [
2630
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: S.muted, children: items ? items.length + " 个场景块" : "加载中…" }),
2631
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: S.grow }),
2632
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(NButton, { onClick: load, children: "刷新" })
2428
2633
  ] }),
2429
- error ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: S.error, children: error }) : null,
2430
- items && items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: S.intro, children: "暂无场景块。累计 5 条新记忆后 L2 会自动整合出第一个场景。" }) : null,
2634
+ error ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: S.error, children: error }) : null,
2635
+ items && items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { style: S.intro, children: "暂无场景块。累计 5 条新记忆后 L2 会自动整合出第一个场景。" }) : null,
2431
2636
  (items || []).map((s) => {
2432
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SceneCard, { s }, s.path);
2637
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SceneCard, { s }, s.path);
2433
2638
  })
2434
2639
  ] });
2435
2640
  }
2436
2641
 
2437
2642
  // client/src/panel.tsx
2438
- var import_jsx_runtime13 = require("react/jsx-runtime");
2643
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2439
2644
  var TABS = [
2440
2645
  ["overview", "概览"],
2441
2646
  ["records", "记忆"],
@@ -2446,23 +2651,23 @@ var __defProp = Object.defineProperty;
2446
2651
  ];
2447
2652
  function MemoryPanel(props) {
2448
2653
  const rpc = props.rpc;
2449
- const [tab, setTab] = (0, import_react13.useState)("overview");
2654
+ const [tab, setTab] = (0, import_react14.useState)("overview");
2450
2655
  ensureThemeStyle();
2451
- (0, import_react13.useEffect)(() => {
2656
+ (0, import_react14.useEffect)(() => {
2452
2657
  watchSidebarIcon();
2453
2658
  }, []);
2454
2659
  let body;
2455
- if (tab === "overview") body = /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(OverviewTab, { rpc });
2456
- else if (tab === "records") body = /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(RecordsTab, { rpc });
2457
- else if (tab === "scenes") body = /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ScenesTab, { rpc });
2458
- else if (tab === "persona") body = /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PersonaTab, { rpc });
2459
- else if (tab === "cost") body = /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(CostTab, { rpc });
2460
- else body = /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(LogTab, { rpc });
2461
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "dsh-mem-root", style: S.section, children: [
2462
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { style: S.heading, children: "记忆 (Memory)" }),
2463
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { style: S.intro, children: "L0~L3 分层蒸馏记忆:浏览被记住的内容,控制记忆模式开关。" }),
2464
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: S.tabbar, children: TABS.map((t) => {
2465
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2660
+ if (tab === "overview") body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(OverviewTab, { rpc });
2661
+ else if (tab === "records") body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(RecordsTab, { rpc });
2662
+ else if (tab === "scenes") body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ScenesTab, { rpc });
2663
+ else if (tab === "persona") body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(PersonaTab, { rpc });
2664
+ else if (tab === "cost") body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(CostTab, { rpc });
2665
+ else body = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(LogTab, { rpc });
2666
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "dsh-mem-root", style: S.section, children: [
2667
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h2", { style: S.heading, children: "记忆 (Memory)" }),
2668
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { style: S.intro, children: "L0~L3 分层蒸馏记忆:浏览被记住的内容,控制记忆模式开关。" }),
2669
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: S.tabbar, children: TABS.map((t) => {
2670
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2466
2671
  "button",
2467
2672
  {
2468
2673
  className: tab === t[0] ? "dsh-mem-tab dsh-mem-tab-on" : "dsh-mem-tab",
@@ -2479,26 +2684,339 @@ var __defProp = Object.defineProperty;
2479
2684
  }
2480
2685
 
2481
2686
  // client/src/pill/MemoryModePill.tsx
2482
- var import_react16 = require("react");
2687
+ var import_react17 = require("react");
2688
+
2689
+ // src/util/context-occupancy.ts
2690
+ var CONTEXT_METER_CIRCUMFERENCE = 34.55751918948772;
2691
+ function haloDashArray(occupancyRatio, circumference = CONTEXT_METER_CIRCUMFERENCE, minLen = 0) {
2692
+ const clamped = Number.isFinite(occupancyRatio) ? Math.min(1, Math.max(0, occupancyRatio)) : 0;
2693
+ const len = Math.max(clamped * circumference, clamped > 0 ? minLen : 0);
2694
+ return `${len} ${circumference}`;
2695
+ }
2696
+ var RADIUS_EPSILON = 1e-6;
2697
+ function isContextMeterAnchor(sig) {
2698
+ if (sig.ariaHasPopup !== "dialog") return false;
2699
+ if (sig.viewBox !== "0 0 14 14") return false;
2700
+ const radii = sig.circleRadii;
2701
+ if (!Array.isArray(radii) || radii.length !== 2) return false;
2702
+ return radii.every((r) => Math.abs(r - 5.5) < RADIUS_EPSILON);
2703
+ }
2704
+
2705
+ // client/src/meter/occupancy-indicator.ts
2706
+ var statsCall = null;
2707
+ var snapshotBySession = /* @__PURE__ */ new Map();
2708
+ var activeSessionId = null;
2709
+ var panelListeners = /* @__PURE__ */ new Set();
2710
+ var panelWasOpen = false;
2711
+ var snapshotListeners = /* @__PURE__ */ new Set();
2712
+ function initOccupancyIndicator(call) {
2713
+ statsCall = call;
2714
+ }
2715
+ function noteOccupancySession(sessionId) {
2716
+ if (sessionId === activeSessionId) return;
2717
+ activeSessionId = sessionId;
2718
+ panelWasOpen = false;
2719
+ if (sessionId !== null && !snapshotBySession.has(sessionId)) void fetchSnapshot(true);
2720
+ }
2721
+ function onMeterPanelOpen(listener) {
2722
+ panelListeners.add(listener);
2723
+ return () => panelListeners.delete(listener);
2724
+ }
2725
+ function onMeterSnapshotUpdate(listener) {
2726
+ snapshotListeners.add(listener);
2727
+ return () => snapshotListeners.delete(listener);
2728
+ }
2729
+ function effectiveView(snap) {
2730
+ const recall = Math.max(snap?.backfillRecallTokens ?? 0, snap?.recallTokens ?? 0);
2731
+ const ledgerProfile = snap?.profileTokens ?? 0;
2732
+ const profile = ledgerProfile > 0 ? ledgerProfile : snap?.backfillProfileTokens ?? 0;
2733
+ return { stock: recall + profile, recall, profile, window: snap?.contextWindowTokens ?? null };
2734
+ }
2735
+ function currentMeterSnapshot() {
2736
+ const sid = activeSessionId;
2737
+ if (sid === null) return null;
2738
+ const snap = snapshotBySession.get(sid);
2739
+ if (!snap) return null;
2740
+ const v = effectiveView(snap);
2741
+ return {
2742
+ stockTokens: v.stock,
2743
+ recallTokens: v.recall,
2744
+ profileTokens: v.profile,
2745
+ contextWindowTokens: v.window,
2746
+ mode: snap.mode
2747
+ };
2748
+ }
2749
+ function currentAnchor() {
2750
+ return anchorCache?.button ?? null;
2751
+ }
2752
+ var FETCH_MIN_INTERVAL_MS = 2e3;
2753
+ var FETCH_FAILURE_REMOVE_AFTER = 3;
2754
+ var lastFetchStartedAt = 0;
2755
+ var fetchInFlight = false;
2756
+ var fetchFailureStreak = 0;
2757
+ async function fetchSnapshot(force = false) {
2758
+ const sid = activeSessionId;
2759
+ if (!statsCall || !sid || fetchInFlight) return;
2760
+ if (!force && Date.now() - lastFetchStartedAt < FETCH_MIN_INTERVAL_MS) return;
2761
+ fetchInFlight = true;
2762
+ lastFetchStartedAt = Date.now();
2763
+ try {
2764
+ const res = await statsCall("dsh-memory/session-stats", { sessionId: sid });
2765
+ const v = res && res.ok ? res.value : void 0;
2766
+ if (!res || !res.ok) {
2767
+ fetchFailureStreak++;
2768
+ scheduleReconcile();
2769
+ return;
2770
+ }
2771
+ if (sid !== activeSessionId) return;
2772
+ snapshotBySession.set(sid, {
2773
+ stockTokens: v?.supported && v.memoryOccupancy ? v.memoryOccupancy.stockTokens : null,
2774
+ recallTokens: v?.supported && v.memoryOccupancy ? v.memoryOccupancy.recallTokens : null,
2775
+ profileTokens: v?.supported && v.memoryOccupancy ? v.memoryOccupancy.profileTokens : null,
2776
+ backfillRecallTokens: v?.supported ? v.occupancyBackfill?.recallTokens ?? null : null,
2777
+ backfillProfileTokens: v?.supported ? v.occupancyBackfill?.profileTokens ?? null : null,
2778
+ contextWindowTokens: v?.supported ? v.contextWindowTokens ?? null : null,
2779
+ mode: v?.supported ? v.mode ?? null : null,
2780
+ updatedAt: Date.now()
2781
+ });
2782
+ fetchFailureStreak = 0;
2783
+ for (const l of snapshotListeners) l();
2784
+ scheduleReconcile();
2785
+ } catch {
2786
+ fetchFailureStreak++;
2787
+ scheduleReconcile();
2788
+ } finally {
2789
+ fetchInFlight = false;
2790
+ }
2791
+ }
2792
+ var PARASITE_CLASS = "dsh-mem-parasite";
2793
+ var MIN_VISIBLE_ARC = 2;
2794
+ var observer = null;
2795
+ var reconcileScheduled = false;
2796
+ var anchorCache = null;
2797
+ function watchContextMeter() {
2798
+ if (observer !== null || typeof document === "undefined" || !document.body) return;
2799
+ observer = new MutationObserver(onMutations);
2800
+ observer.observe(document.body, {
2801
+ childList: true,
2802
+ subtree: true,
2803
+ attributes: true,
2804
+ attributeFilter: ["stroke-dasharray", "aria-expanded", "aria-label"]
2805
+ });
2806
+ scheduleReconcile();
2807
+ }
2808
+ function onMutations(mutations) {
2809
+ for (const m of mutations) {
2810
+ const target = m.target;
2811
+ if (target && typeof target.classList?.contains === "function" && target.classList.contains(PARASITE_CLASS)) {
2812
+ continue;
2813
+ }
2814
+ scheduleReconcile();
2815
+ return;
2816
+ }
2817
+ }
2818
+ function scheduleReconcile() {
2819
+ if (reconcileScheduled) return;
2820
+ reconcileScheduled = true;
2821
+ queueMicrotask(() => {
2822
+ reconcileScheduled = false;
2823
+ reconcile();
2824
+ });
2825
+ }
2826
+ function findAnchor() {
2827
+ const candidates = document.querySelectorAll('button[aria-haspopup="dialog"]');
2828
+ for (let i = 0; i < candidates.length; i++) {
2829
+ const button = candidates[i];
2830
+ if (!button) continue;
2831
+ const svg = button.querySelector("svg");
2832
+ if (!svg) continue;
2833
+ const circles = svg.querySelectorAll("circle");
2834
+ const radii = [];
2835
+ for (let c = 0; c < circles.length; c++) {
2836
+ const r = parseFloat(circles[c]?.getAttribute("r") ?? "");
2837
+ if (Number.isFinite(r)) radii.push(r);
2838
+ }
2839
+ if (isContextMeterAnchor({
2840
+ ariaHasPopup: button.getAttribute("aria-haspopup"),
2841
+ viewBox: svg.getAttribute("viewBox"),
2842
+ circleRadii: radii
2843
+ })) {
2844
+ return { button, svg };
2845
+ }
2846
+ }
2847
+ return null;
2848
+ }
2849
+ function reconcile() {
2850
+ if (!anchorCache || !anchorCache.button.isConnected || !anchorCache.svg.isConnected) {
2851
+ const found = document.body ? findAnchor() : null;
2852
+ anchorCache = found ? found : null;
2853
+ }
2854
+ if (!anchorCache) return;
2855
+ const open = anchorCache.button.getAttribute("aria-expanded") === "true";
2856
+ if (open !== panelWasOpen) {
2857
+ panelWasOpen = open;
2858
+ for (const l of panelListeners) l(open);
2859
+ if (open) void fetchSnapshot(true);
2860
+ }
2861
+ applyHalo();
2862
+ }
2863
+ function applyHalo() {
2864
+ const svg = anchorCache?.svg;
2865
+ if (!svg) return;
2866
+ const snap = activeSessionId !== null ? snapshotBySession.get(activeSessionId) : void 0;
2867
+ const failing = fetchFailureStreak >= FETCH_FAILURE_REMOVE_AFTER;
2868
+ const view = failing ? { stock: 0, recall: 0, profile: 0, window: null } : effectiveView(snap);
2869
+ const win = view.window;
2870
+ const ratio = win !== null && win > 0 ? Math.min(1, Math.max(0, view.stock / win)) : null;
2871
+ const existing = svg.querySelector(`circle.${PARASITE_CLASS}`);
2872
+ if (ratio === null || ratio <= 0) {
2873
+ existing?.remove();
2874
+ return;
2875
+ }
2876
+ let halo = existing;
2877
+ if (!halo) {
2878
+ halo = document.createElementNS("http://www.w3.org/2000/svg", "circle");
2879
+ halo.setAttribute("class", PARASITE_CLASS);
2880
+ halo.setAttribute("cx", "7");
2881
+ halo.setAttribute("cy", "7");
2882
+ halo.setAttribute("r", "6.4");
2883
+ halo.setAttribute("fill", "none");
2884
+ halo.setAttribute("stroke", "var(--dsh-mem-accent)");
2885
+ halo.setAttribute("stroke-width", "0.85");
2886
+ halo.setAttribute("transform", "rotate(-90 7 7)");
2887
+ halo.style.filter = "drop-shadow(0 0 3px var(--dsh-mem-accent))";
2888
+ halo.setAttribute("aria-hidden", "true");
2889
+ halo.style.pointerEvents = "none";
2890
+ svg.appendChild(halo);
2891
+ }
2892
+ halo.setAttribute(
2893
+ "stroke-dasharray",
2894
+ haloDashArray(ratio, CONTEXT_METER_CIRCUMFERENCE, MIN_VISIBLE_ARC)
2895
+ );
2896
+ const fill = svg.querySelector("circle:nth-of-type(2)");
2897
+ const offKey = fill?.getAttribute("stroke-dasharray") ?? "";
2898
+ if (svg.dataset.offKey !== void 0 && svg.dataset.offKey !== offKey) {
2899
+ void fetchSnapshot();
2900
+ }
2901
+ svg.dataset.offKey = offKey;
2902
+ }
2903
+
2904
+ // client/src/meter/panel-section.ts
2905
+ var SECTION_TAG = "dsh-mem-panel";
2906
+ var inited = false;
2907
+ var mounted = null;
2908
+ var panelOpen = false;
2909
+ function fmtTokens(n) {
2910
+ if (n >= 1e6) {
2911
+ const v = n / 1e6;
2912
+ return `${v < 100 ? v.toFixed(1) : Math.round(v)}M`;
2913
+ }
2914
+ if (n >= 1e3) {
2915
+ const v = n / 1e3;
2916
+ return `${v < 100 ? v.toFixed(1) : Math.round(v)}K`;
2917
+ }
2918
+ return String(Math.round(n));
2919
+ }
2920
+ function initPanelSection(read) {
2921
+ if (inited) return;
2922
+ inited = true;
2923
+ onMeterPanelOpen((open) => {
2924
+ panelOpen = open;
2925
+ if (!open) {
2926
+ mounted?.remove();
2927
+ mounted = null;
2928
+ return;
2929
+ }
2930
+ tryMount(read);
2931
+ });
2932
+ onMeterSnapshotUpdate(() => {
2933
+ if (panelOpen && !mounted) tryMount(read);
2934
+ });
2935
+ }
2936
+ function tryMount(read) {
2937
+ const view = read();
2938
+ if (!view || view.stockTokens === null || view.stockTokens <= 0) return;
2939
+ const anchor = currentAnchor();
2940
+ if (!anchor || document.activeElement !== anchor) return;
2941
+ const target = findDialogRoot();
2942
+ if (!target) return;
2943
+ mounted?.remove();
2944
+ mounted = renderSection(view);
2945
+ target.appendChild(mounted);
2946
+ }
2947
+ function findDialogRoot() {
2948
+ const dialogs = document.querySelectorAll('[role="dialog"]');
2949
+ for (let i = dialogs.length - 1; i >= 0; i--) {
2950
+ const el = dialogs[i];
2951
+ if (!el || !el.isConnected) continue;
2952
+ if (el.querySelector(`[data-${SECTION_TAG}]`) || el.hasAttribute(`data-${SECTION_TAG}`)) continue;
2953
+ const style = window.getComputedStyle(el);
2954
+ if (style.display === "none" || style.visibility === "hidden") continue;
2955
+ return el;
2956
+ }
2957
+ return null;
2958
+ }
2959
+ function row(dotColor, label, tokens) {
2960
+ const div = document.createElement("div");
2961
+ div.style.cssText = "display:flex;justify-content:space-between;align-items:center;padding:3px 0;font-size:12px;";
2962
+ const left = document.createElement("span");
2963
+ left.style.cssText = `display:inline-flex;align-items:center;gap:6px;color:var(--dsh-mem-text-2);`;
2964
+ const dot = document.createElement("i");
2965
+ dot.style.cssText = `width:7px;height:7px;border-radius:50%;background:${dotColor};display:inline-block;`;
2966
+ left.append(dot, document.createTextNode(label));
2967
+ const right = document.createElement("span");
2968
+ right.textContent = `~${fmtTokens(tokens)}`;
2969
+ right.style.cssText = "font-variant-numeric:tabular-nums;color:var(--dsh-mem-text-1);";
2970
+ div.append(left, right);
2971
+ return div;
2972
+ }
2973
+ function renderSection(view) {
2974
+ const section = document.createElement("section");
2975
+ section.setAttribute(`data-${SECTION_TAG}`, "");
2976
+ section.style.cssText = [
2977
+ "border-top:1px solid var(--dsh-mem-border)",
2978
+ "margin-top:6px",
2979
+ "padding-top:8px",
2980
+ "font-size:12px",
2981
+ "line-height:1.5"
2982
+ ].join(";");
2983
+ const title = document.createElement("div");
2984
+ title.textContent = "记忆占用";
2985
+ title.style.cssText = "color:var(--dsh-mem-text-2);font-weight:600;margin-bottom:4px;";
2986
+ section.append(title);
2987
+ if (view.recallTokens !== null && view.recallTokens > 0) {
2988
+ section.append(row("var(--dsh-mem-accent)", "召回片段", view.recallTokens));
2989
+ }
2990
+ if (view.profileTokens !== null && view.profileTokens > 0) {
2991
+ section.append(row("var(--dsh-mem-accent)", "记忆稳定区", view.profileTokens));
2992
+ }
2993
+ if (view.mode === "off") {
2994
+ const offNote = document.createElement("div");
2995
+ offNote.textContent = "已停用 · 显示现存残留";
2996
+ offNote.style.cssText = "color:var(--dsh-mem-text-3);padding-top:2px;";
2997
+ section.append(offNote);
2998
+ }
2999
+ return section;
3000
+ }
2483
3001
 
2484
3002
  // client/src/pill/ModeSlider.tsx
2485
- var import_react15 = require("react");
3003
+ var import_react16 = require("react");
2486
3004
 
2487
3005
  // client/src/pill/SessionInfoArea.tsx
2488
- var import_react14 = require("react");
2489
- var import_jsx_runtime14 = require("react/jsx-runtime");
3006
+ var import_react15 = require("react");
3007
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2490
3008
  function sinfoCell(val, label, title) {
2491
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { title: title || void 0, children: [
2492
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "dsh-mem-sinfo-val", children: val }),
2493
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "dsh-mem-sinfo-label", children: label })
3009
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { title: title || void 0, children: [
3010
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "dsh-mem-sinfo-val", children: val }),
3011
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "dsh-mem-sinfo-label", children: label })
2494
3012
  ] });
2495
3013
  }
2496
3014
  function SessionInfoArea(props) {
2497
3015
  const rpc = props.rpc;
2498
3016
  const sessionId = props.sessionId;
2499
- const [stats, setStats] = (0, import_react14.useState)(void 0);
2500
- const busyRef = (0, import_react14.useRef)(false);
2501
- (0, import_react14.useEffect)(() => {
3017
+ const [stats, setStats] = (0, import_react15.useState)(void 0);
3018
+ const busyRef = (0, import_react15.useRef)(false);
3019
+ (0, import_react15.useEffect)(() => {
2502
3020
  if (!rpc || !sessionId) return void 0;
2503
3021
  let alive = true;
2504
3022
  let timer = null;
@@ -2531,7 +3049,7 @@ var __defProp = Object.defineProperty;
2531
3049
  }, [rpc, sessionId]);
2532
3050
  if (stats === null) return null;
2533
3051
  if (stats === void 0) {
2534
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "dsh-mem-sinfo", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "dsh-mem-sinfo-grid", children: [
3052
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "dsh-mem-sinfo", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "dsh-mem-sinfo-grid", children: [
2535
3053
  sinfoCell("…", "召回命中"),
2536
3054
  sinfoCell("…", "攒批进度"),
2537
3055
  sinfoCell("…", "本会话记忆"),
@@ -2574,27 +3092,27 @@ var __defProp = Object.defineProperty;
2574
3092
  else if (stats.retrieval === "none") note = "检索不可用(FTS 与向量均失效)";
2575
3093
  }
2576
3094
  const ago = fmtAgo(gl.lastExtractAt);
2577
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "dsh-mem-sinfo", children: [
2578
- warn ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "dsh-mem-sinfo-warn", children: warn }) : null,
2579
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "dsh-mem-sinfo-grid", children: [
3095
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "dsh-mem-sinfo", children: [
3096
+ warn ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "dsh-mem-sinfo-warn", children: warn }) : null,
3097
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "dsh-mem-sinfo-grid", children: [
2580
3098
  sinfoCell(rcVal, rcLabel, rcTitle),
2581
3099
  sinfoCell(dVal, dLabel, dTitle),
2582
3100
  sinfoCell(String(di.producedRecords || 0), "本会话记忆", pTitle),
2583
3101
  sinfoCell(stats.l0Count != null ? String(stats.l0Count) : "…", "会话消息")
2584
3102
  ] }),
2585
- note ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "dsh-mem-sinfo-note", children: note }) : null,
2586
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "dsh-mem-sinfo-sum", children: "待蒸馏 " + (gl.pendingTotal || 0) + " · 上次蒸馏 " + (ago || "尚未蒸馏") })
3103
+ note ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "dsh-mem-sinfo-note", children: note }) : null,
3104
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "dsh-mem-sinfo-sum", children: "待蒸馏 " + (gl.pendingTotal || 0) + " · 上次蒸馏 " + (ago || "尚未蒸馏") })
2587
3105
  ] });
2588
3106
  }
2589
3107
 
2590
3108
  // client/src/pill/ModeSlider.tsx
2591
- var import_jsx_runtime15 = require("react/jsx-runtime");
3109
+ var import_jsx_runtime16 = require("react/jsx-runtime");
2592
3110
  function ModeSlider(props) {
2593
3111
  ensureThemeStyle();
2594
- const trackRef = (0, import_react15.useRef)(null);
2595
- const [drag, setDrag] = (0, import_react15.useState)(null);
2596
- const canvasRef = (0, import_react15.useRef)(null);
2597
- const geoRef = (0, import_react15.useRef)(null);
3112
+ const trackRef = (0, import_react16.useRef)(null);
3113
+ const [drag, setDrag] = (0, import_react16.useState)(null);
3114
+ const canvasRef = (0, import_react16.useRef)(null);
3115
+ const geoRef = (0, import_react16.useRef)(null);
2598
3116
  const clampX = (x) => {
2599
3117
  if (x < 0) return 0;
2600
3118
  if (x > INNER_W) return INNER_W;
@@ -2642,7 +3160,7 @@ var __defProp = Object.defineProperty;
2642
3160
  // 与填充显隐同源
2643
3161
  dragging: drag !== null
2644
3162
  };
2645
- (0, import_react15.useEffect)(() => {
3163
+ (0, import_react16.useEffect)(() => {
2646
3164
  const canvas = canvasRef.current;
2647
3165
  if (!canvas) return void 0;
2648
3166
  const ctx = canvas.getContext && canvas.getContext("2d");
@@ -2667,14 +3185,14 @@ var __defProp = Object.defineProperty;
2667
3185
  ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
2668
3186
  cell = width < 280 ? 5 : 6;
2669
3187
  grid = [];
2670
- for (let row = 0; row * cell < height; row++) {
3188
+ for (let row2 = 0; row2 * cell < height; row2++) {
2671
3189
  for (let column = 0; column * cell < width; column++) {
2672
3190
  grid.push({
2673
3191
  x: column * cell,
2674
- y: row * cell,
2675
- base: Math.abs(Math.sin(column * 12.9898 + row * 78.233) * 43758.5453) % 1,
2676
- tempo: Math.abs(Math.sin(column * 7.13 + row * 19.41) * 19341.731) % 1,
2677
- phase: Math.abs(Math.sin(column * 31.17 + row * 11.93) * 28437.123) % 1
3192
+ y: row2 * cell,
3193
+ base: Math.abs(Math.sin(column * 12.9898 + row2 * 78.233) * 43758.5453) % 1,
3194
+ tempo: Math.abs(Math.sin(column * 7.13 + row2 * 19.41) * 19341.731) % 1,
3195
+ phase: Math.abs(Math.sin(column * 31.17 + row2 * 11.93) * 28437.123) % 1
2678
3196
  });
2679
3197
  }
2680
3198
  }
@@ -2752,7 +3270,7 @@ var __defProp = Object.defineProperty;
2752
3270
  for (let i = 0; i < MODES.length; i++) {
2753
3271
  const stopLeft = i / (MODES.length - 1) * INNER_W + THUMB / 2;
2754
3272
  stops.push(
2755
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3273
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2756
3274
  "div",
2757
3275
  {
2758
3276
  style: {
@@ -2771,7 +3289,7 @@ var __defProp = Object.defineProperty;
2771
3289
  )
2772
3290
  );
2773
3291
  }
2774
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3292
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2775
3293
  "div",
2776
3294
  {
2777
3295
  style: {
@@ -2781,13 +3299,13 @@ var __defProp = Object.defineProperty;
2781
3299
  transform: "translateX(-50%)",
2782
3300
  zIndex: 1e3
2783
3301
  },
2784
- children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
3302
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
2785
3303
  "div",
2786
3304
  {
2787
3305
  className: "dsh-mem-popover",
2788
3306
  style: { position: "relative", padding: "14px 16px" },
2789
3307
  children: [
2790
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
3308
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
2791
3309
  "div",
2792
3310
  {
2793
3311
  ref: trackRef,
@@ -2806,7 +3324,7 @@ var __defProp = Object.defineProperty;
2806
3324
  onPointerUp,
2807
3325
  onPointerCancel: onPointerUp,
2808
3326
  children: [
2809
- activeIdx > 0 || drag !== null ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3327
+ activeIdx > 0 || drag !== null ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2810
3328
  "div",
2811
3329
  {
2812
3330
  style: {
@@ -2824,7 +3342,7 @@ var __defProp = Object.defineProperty;
2824
3342
  }
2825
3343
  ) : null,
2826
3344
  stops,
2827
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3345
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2828
3346
  "canvas",
2829
3347
  {
2830
3348
  ref: canvasRef,
@@ -2841,7 +3359,7 @@ var __defProp = Object.defineProperty;
2841
3359
  }
2842
3360
  }
2843
3361
  ),
2844
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3362
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2845
3363
  "div",
2846
3364
  {
2847
3365
  style: {
@@ -2860,12 +3378,12 @@ var __defProp = Object.defineProperty;
2860
3378
  }
2861
3379
  }
2862
3380
  ),
2863
- drag !== null ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "dsh-mem-bubble", style: { left: thumbLeft + THUMB / 2, zIndex: 4 }, children: info.label }) : null
3381
+ drag !== null ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "dsh-mem-bubble", style: { left: thumbLeft + THUMB / 2, zIndex: 4 }, children: info.label }) : null
2864
3382
  ]
2865
3383
  }
2866
3384
  ),
2867
- props.error ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: { fontSize: 11, color: "var(--dsh-mem-danger)", marginTop: 10, whiteSpace: "nowrap" }, children: props.error }) : null,
2868
- props.rpc && props.sessionId ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(SessionInfoArea, { rpc: props.rpc, sessionId: props.sessionId }) : null
3385
+ props.error ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { fontSize: 11, color: "var(--dsh-mem-danger)", marginTop: 10, whiteSpace: "nowrap" }, children: props.error }) : null,
3386
+ props.rpc && props.sessionId ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(SessionInfoArea, { rpc: props.rpc, sessionId: props.sessionId }) : null
2869
3387
  ]
2870
3388
  }
2871
3389
  )
@@ -2874,16 +3392,16 @@ var __defProp = Object.defineProperty;
2874
3392
  }
2875
3393
 
2876
3394
  // client/src/pill/MemoryModePill.tsx
2877
- var import_jsx_runtime16 = require("react/jsx-runtime");
3395
+ var import_jsx_runtime17 = require("react/jsx-runtime");
2878
3396
  function MemoryModePill(props) {
2879
3397
  const rpc = props.rpc;
2880
3398
  const sessionId = props.sessionId || props.session && props.session.sessionId;
2881
- const [mode, setMode] = (0, import_react16.useState)(null);
2882
- const [error, setError] = (0, import_react16.useState)(null);
2883
- const [open, setOpen] = (0, import_react16.useState)(false);
2884
- const wrapRef = (0, import_react16.useRef)(null);
2885
- const seqRef = (0, import_react16.useRef)(0);
2886
- const load = (0, import_react16.useCallback)(() => {
3399
+ const [mode, setMode] = (0, import_react17.useState)(null);
3400
+ const [error, setError] = (0, import_react17.useState)(null);
3401
+ const [open, setOpen] = (0, import_react17.useState)(false);
3402
+ const wrapRef = (0, import_react17.useRef)(null);
3403
+ const seqRef = (0, import_react17.useRef)(0);
3404
+ const load = (0, import_react17.useCallback)(() => {
2887
3405
  if (!sessionId || !rpc) return;
2888
3406
  const token = ++seqRef.current;
2889
3407
  setError(null);
@@ -2896,13 +3414,21 @@ var __defProp = Object.defineProperty;
2896
3414
  setError(String(e && e.message || e));
2897
3415
  });
2898
3416
  }, [sessionId, rpc]);
2899
- (0, import_react16.useEffect)(() => {
3417
+ (0, import_react17.useEffect)(() => {
2900
3418
  load();
2901
3419
  }, [load]);
2902
- (0, import_react16.useEffect)(() => {
3420
+ (0, import_react17.useEffect)(() => {
2903
3421
  watchSidebarIcon();
2904
3422
  }, []);
2905
- (0, import_react16.useEffect)(() => {
3423
+ (0, import_react17.useEffect)(() => {
3424
+ initOccupancyIndicator(
3425
+ (endpoint, payload) => rpc(endpoint, payload)
3426
+ );
3427
+ initPanelSection(currentMeterSnapshot);
3428
+ watchContextMeter();
3429
+ noteOccupancySession(sessionId ?? null);
3430
+ }, [sessionId, rpc]);
3431
+ (0, import_react17.useEffect)(() => {
2906
3432
  if (!open) return;
2907
3433
  const onDown = (e) => {
2908
3434
  if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
@@ -2957,8 +3483,8 @@ var __defProp = Object.defineProperty;
2957
3483
  pillStyle.boxShadow = "0 0 12px color-mix(in srgb, " + info.color + " 30%, transparent)";
2958
3484
  pillStyle["--dsh-mem-pill-tint"] = info.color;
2959
3485
  }
2960
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { ref: wrapRef, style: { position: "relative", display: "inline-flex" }, children: [
2961
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
3486
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { ref: wrapRef, style: { position: "relative", display: "inline-flex" }, children: [
3487
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
2962
3488
  "button",
2963
3489
  {
2964
3490
  type: "button",
@@ -2971,11 +3497,11 @@ var __defProp = Object.defineProperty;
2971
3497
  style: pillStyle,
2972
3498
  children: [
2973
3499
  "记忆 · ",
2974
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: loaded ? info.label : error ? "⚠" : "…" })
3500
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { children: loaded ? info.label : error ? "⚠" : "…" })
2975
3501
  ]
2976
3502
  }
2977
3503
  ),
2978
- open ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ModeSlider, { mode: mode || "auto", onCommit: commit, error, rpc, sessionId }) : null
3504
+ open ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ModeSlider, { mode: mode || "auto", onCommit: commit, error, rpc, sessionId }) : null
2979
3505
  ] });
2980
3506
  }
2981
3507