dsh-memory-engine 0.8.1 → 0.8.2

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/lib/client.js CHANGED
@@ -597,7 +597,8 @@ function frame(now) {
597
597
 
598
598
  var defaultSettings = { hideButton: false, showGuideOnEmpty: true, graphTheme: "classic", graphLayout: "sphere", autoMemory: { enabled: false, maxPerTurn: 2, dailyCap: 40 }, showAutoMemoryCostHint: true };
599
599
  var actionStyle = { padding: "7px 12px", borderRadius: "8px", border: "1px solid var(--memory-border,#6999b8)", background: "var(--memory-button,#102236)", color: "var(--memory-text,#e2f4ff)", font: "inherit", cursor: "pointer" };
600
- var fieldStyle = { boxSizing: "border-box", padding: "10px 12px", borderRadius: "8px", border: "1px solid #35536b", background: "#091422", color: "#e2f4ff", font: "inherit", minWidth: 0 };
600
+ var closeStyle = Object.assign({}, actionStyle, { padding: "11px 20px", minHeight: "42px", fontWeight: 700, letterSpacing: "0.02em" });
601
+ var fieldStyle = { boxSizing: "border-box", padding: "10px 12px", borderRadius: "8px", border: "1px solid #35536b", background: "#091422", color: "#e2f4ff", font: "inherit", minWidth: 0 };
601
602
  var kinds = ["知识", "印象", "对话", "事件", "日记"];
602
603
  var appearanceOptions = [
603
604
  { key: "graphTheme", label: "配色", choices: [["classic", "经典星空"], ["neon", "赛博霓虹"], ["paper", "铅绘素描"], ["mono", "极简单色"]] },
@@ -766,7 +767,7 @@ function frame(now) {
766
767
  style: { position: "absolute", top: "72px", right: "18px", zIndex: 1, width: "min(350px,calc(100% - 36px))", maxHeight: "calc(100% - 96px)", boxSizing: "border-box", display: "flex", flexDirection: "column", overflow: "hidden", padding: "18px", border: "1px solid #3976a0", borderRadius: "14px", background: "#07111fee", color: "#dbeaf8", font: "14px/1.6 system-ui,sans-serif", boxShadow: "0 0 40px #1763a033", backdropFilter: "blur(8px)", animation: "dsh-memory-detail-in 180ms ease-out" },
767
768
  }, React.createElement("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "12px", flexShrink: 0 } },
768
769
  React.createElement("span", { "data-memory-engine-detail-kind": "true", style: { color: "#a4e6ff", background: "#15344b", borderRadius: "6px", padding: "2px 8px", overflowWrap: "anywhere" } }, node.kind || "未分类"),
769
- React.createElement("button", { type: "button", "aria-label": "关闭记忆详情", onClick: props.onClose, style: { border: 0, padding: "2px 6px", background: "transparent", color: "#a4bad0", fontSize: "22px", cursor: "pointer" } }, "×")),
770
+ React.createElement("button", { type: "button", "aria-label": "关闭记忆详情", onClick: props.onClose, style: Object.assign({ minWidth: "40px", minHeight: "40px", fontSize: "18px", lineHeight: 1, cursor: "pointer" }, { border: 0, padding: "2px 6px", background: "transparent", color: "#a4bad0", fontSize: "22px", cursor: "pointer" }) }, "×")),
770
771
  React.createElement("h2", { "data-memory-engine-detail-title": "true", style: { margin: "10px 0", fontSize: "18px", color: "#b7e9ff", overflowWrap: "anywhere", whiteSpace: "normal", maxHeight: "18vh", overflowY: "auto", flexShrink: 0 } }, title),
771
772
  node.isCore ? null : React.createElement("dl", { style: { display: "grid", gridTemplateColumns: "64px minmax(0,1fr)", gap: "5px 10px", margin: "0 0 12px", fontSize: "12px", color: "#9fb7ce", maxHeight: "25vh", overflowY: "auto", flexShrink: 0 } },
772
773
  rows.map(function (row) { return React.createElement(React.Fragment, { key: row[0] },
@@ -1217,7 +1218,7 @@ function frame(now) {
1217
1218
  return function () { if (configRequest.current) configRequest.current.abort(); };
1218
1219
  }, []);
1219
1220
  React.useEffect(function () {
1220
- if (!open) return undefined;
1221
+ if (!open || typeof window === "undefined" || typeof window.addEventListener !== "function") return undefined;
1221
1222
  var controller = new AbortController();
1222
1223
  setConfigBusy(true);
1223
1224
  readApi("/memory-engine/api/config", controller.signal).then(function (data) {
@@ -1309,7 +1310,7 @@ function frame(now) {
1309
1310
  if (gesture && gesture.moved < 6 && gesture.version === selectionVersion.current) setSelected(null);
1310
1311
  }
1311
1312
  React.useEffect(function () {
1312
- if (!open) return undefined;
1313
+ if (!open || typeof window === "undefined" || typeof window.addEventListener !== "function") return undefined;
1313
1314
  var controller = new AbortController();
1314
1315
  var active = true;
1315
1316
  setGuideData(null);
@@ -1327,7 +1328,7 @@ function frame(now) {
1327
1328
  return function () { active = false; controller.abort(); };
1328
1329
  }, [open, retry, settings.showGuideOnEmpty]);
1329
1330
  React.useEffect(function () {
1330
- if (!open) return undefined;
1331
+ if (!open || typeof window === "undefined" || typeof window.addEventListener !== "function") return undefined;
1331
1332
  function onEscape() {
1332
1333
  if (guideOpen) closeGuide();
1333
1334
  else if (appearanceOpen) { setAppearanceOpen(false); if (appearanceButton.current) appearanceButton.current.focus(); }
@@ -1386,12 +1387,13 @@ function frame(now) {
1386
1387
  type: "button",
1387
1388
  onClick: closeMap,
1388
1389
  "aria-label": "关闭记忆图谱",
1389
- style: actionStyle,
1390
+ title: "关闭(Esc)",
1391
+ style: closeStyle,
1390
1392
  }, "关闭 ×")), appearanceOpen ? React.createElement("section", { ref: appearancePanel, role: "dialog", "aria-label": "外观", "data-memory-engine-appearance-panel": "true",
1391
1393
  style: { position: "absolute", top: "62px", right: "18px", zIndex: 3, boxSizing: "border-box", width: "min(380px,calc(100vw - 36px))", maxHeight: "calc(100vh - 90px)", overflowY: "auto",
1392
1394
  padding: "18px", border: "1px solid var(--memory-border,#3976a0)", borderRadius: "12px", background: "var(--memory-panel,#07111f)", boxShadow: "0 12px 35px #0004" } },
1393
1395
  React.createElement("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "14px", color: "var(--memory-text,#dbeaf8)" } },
1394
- React.createElement("strong", null, "外观"), React.createElement("button", { type: "button", "aria-label": "关闭外观", style: actionStyle,
1396
+ React.createElement("strong", null, "外观"), React.createElement("button", { type: "button", "aria-label": "关闭外观", style: closeStyle,
1395
1397
  onClick: function () { setAppearanceOpen(false); if (appearanceButton.current) appearanceButton.current.focus(); } }, "×")),
1396
1398
  React.createElement(AppearanceControls, { settings: settings, busy: configBusy, onChange: saveAppearance, location: "header" })) : null,
1397
1399
  configError ? React.createElement("p", { role: "alert", "data-memory-engine-config-error": "true",
package/lib/client.src.js CHANGED
@@ -18,7 +18,8 @@ window.__ModuleLoader__.load({
18
18
 
19
19
  var defaultSettings = { hideButton: false, showGuideOnEmpty: true, graphTheme: "classic", graphLayout: "sphere", autoMemory: { enabled: false, maxPerTurn: 2, dailyCap: 40 }, showAutoMemoryCostHint: true };
20
20
  var actionStyle = { padding: "7px 12px", borderRadius: "8px", border: "1px solid var(--memory-border,#6999b8)", background: "var(--memory-button,#102236)", color: "var(--memory-text,#e2f4ff)", font: "inherit", cursor: "pointer" };
21
- var fieldStyle = { boxSizing: "border-box", padding: "10px 12px", borderRadius: "8px", border: "1px solid #35536b", background: "#091422", color: "#e2f4ff", font: "inherit", minWidth: 0 };
21
+ var closeStyle = Object.assign({}, actionStyle, { padding: "11px 20px", minHeight: "42px", fontWeight: 700, letterSpacing: "0.02em" });
22
+ var fieldStyle = { boxSizing: "border-box", padding: "10px 12px", borderRadius: "8px", border: "1px solid #35536b", background: "#091422", color: "#e2f4ff", font: "inherit", minWidth: 0 };
22
23
  var kinds = ["知识", "印象", "对话", "事件", "日记"];
23
24
  var appearanceOptions = [
24
25
  { key: "graphTheme", label: "配色", choices: [["classic", "经典星空"], ["neon", "赛博霓虹"], ["paper", "铅绘素描"], ["mono", "极简单色"]] },
@@ -187,7 +188,7 @@ window.__ModuleLoader__.load({
187
188
  style: { position: "absolute", top: "72px", right: "18px", zIndex: 1, width: "min(350px,calc(100% - 36px))", maxHeight: "calc(100% - 96px)", boxSizing: "border-box", display: "flex", flexDirection: "column", overflow: "hidden", padding: "18px", border: "1px solid #3976a0", borderRadius: "14px", background: "#07111fee", color: "#dbeaf8", font: "14px/1.6 system-ui,sans-serif", boxShadow: "0 0 40px #1763a033", backdropFilter: "blur(8px)", animation: "dsh-memory-detail-in 180ms ease-out" },
188
189
  }, React.createElement("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "12px", flexShrink: 0 } },
189
190
  React.createElement("span", { "data-memory-engine-detail-kind": "true", style: { color: "#a4e6ff", background: "#15344b", borderRadius: "6px", padding: "2px 8px", overflowWrap: "anywhere" } }, node.kind || "未分类"),
190
- React.createElement("button", { type: "button", "aria-label": "关闭记忆详情", onClick: props.onClose, style: { border: 0, padding: "2px 6px", background: "transparent", color: "#a4bad0", fontSize: "22px", cursor: "pointer" } }, "×")),
191
+ React.createElement("button", { type: "button", "aria-label": "关闭记忆详情", onClick: props.onClose, style: Object.assign({ minWidth: "40px", minHeight: "40px", fontSize: "18px", lineHeight: 1, cursor: "pointer" }, { border: 0, padding: "2px 6px", background: "transparent", color: "#a4bad0", fontSize: "22px", cursor: "pointer" }) }, "×")),
191
192
  React.createElement("h2", { "data-memory-engine-detail-title": "true", style: { margin: "10px 0", fontSize: "18px", color: "#b7e9ff", overflowWrap: "anywhere", whiteSpace: "normal", maxHeight: "18vh", overflowY: "auto", flexShrink: 0 } }, title),
192
193
  node.isCore ? null : React.createElement("dl", { style: { display: "grid", gridTemplateColumns: "64px minmax(0,1fr)", gap: "5px 10px", margin: "0 0 12px", fontSize: "12px", color: "#9fb7ce", maxHeight: "25vh", overflowY: "auto", flexShrink: 0 } },
193
194
  rows.map(function (row) { return React.createElement(React.Fragment, { key: row[0] },
@@ -638,7 +639,7 @@ window.__ModuleLoader__.load({
638
639
  return function () { if (configRequest.current) configRequest.current.abort(); };
639
640
  }, []);
640
641
  React.useEffect(function () {
641
- if (!open) return undefined;
642
+ if (!open || typeof window === "undefined" || typeof window.addEventListener !== "function") return undefined;
642
643
  var controller = new AbortController();
643
644
  setConfigBusy(true);
644
645
  readApi("/memory-engine/api/config", controller.signal).then(function (data) {
@@ -730,7 +731,7 @@ window.__ModuleLoader__.load({
730
731
  if (gesture && gesture.moved < 6 && gesture.version === selectionVersion.current) setSelected(null);
731
732
  }
732
733
  React.useEffect(function () {
733
- if (!open) return undefined;
734
+ if (!open || typeof window === "undefined" || typeof window.addEventListener !== "function") return undefined;
734
735
  var controller = new AbortController();
735
736
  var active = true;
736
737
  setGuideData(null);
@@ -748,7 +749,7 @@ window.__ModuleLoader__.load({
748
749
  return function () { active = false; controller.abort(); };
749
750
  }, [open, retry, settings.showGuideOnEmpty]);
750
751
  React.useEffect(function () {
751
- if (!open) return undefined;
752
+ if (!open || typeof window === "undefined" || typeof window.addEventListener !== "function") return undefined;
752
753
  function onEscape() {
753
754
  if (guideOpen) closeGuide();
754
755
  else if (appearanceOpen) { setAppearanceOpen(false); if (appearanceButton.current) appearanceButton.current.focus(); }
@@ -807,12 +808,13 @@ window.__ModuleLoader__.load({
807
808
  type: "button",
808
809
  onClick: closeMap,
809
810
  "aria-label": "关闭记忆图谱",
810
- style: actionStyle,
811
+ title: "关闭(Esc)",
812
+ style: closeStyle,
811
813
  }, "关闭 ×")), appearanceOpen ? React.createElement("section", { ref: appearancePanel, role: "dialog", "aria-label": "外观", "data-memory-engine-appearance-panel": "true",
812
814
  style: { position: "absolute", top: "62px", right: "18px", zIndex: 3, boxSizing: "border-box", width: "min(380px,calc(100vw - 36px))", maxHeight: "calc(100vh - 90px)", overflowY: "auto",
813
815
  padding: "18px", border: "1px solid var(--memory-border,#3976a0)", borderRadius: "12px", background: "var(--memory-panel,#07111f)", boxShadow: "0 12px 35px #0004" } },
814
816
  React.createElement("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "14px", color: "var(--memory-text,#dbeaf8)" } },
815
- React.createElement("strong", null, "外观"), React.createElement("button", { type: "button", "aria-label": "关闭外观", style: actionStyle,
817
+ React.createElement("strong", null, "外观"), React.createElement("button", { type: "button", "aria-label": "关闭外观", style: closeStyle,
816
818
  onClick: function () { setAppearanceOpen(false); if (appearanceButton.current) appearanceButton.current.focus(); } }, "×")),
817
819
  React.createElement(AppearanceControls, { settings: settings, busy: configBusy, onChange: saveAppearance, location: "header" })) : null,
818
820
  configError ? React.createElement("p", { role: "alert", "data-memory-engine-config-error": "true",
@@ -0,0 +1,878 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-memory-engine",
3
+ factory: function (require) {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ var React = require("react");
7
+ var ReactDOM = require("react-dom");
8
+ // 2026-10-30:从 views.actions 挪到 header.actions。
9
+ // views.actions 是「更多视图」下拉菜单的行(Chat/Trajectory 旁边的 ⋯),
10
+ // 注册进去只是菜单里多一行,看不见;header.actions 才是头部正常按钮区。
11
+ var SLOT_NAME = "conversation.session.header.actions";
12
+ // 不同 DSH 版本暴露的插槽名不一样:新版本用 header.actions,
13
+ // 老版本没有这个槽时退回 views.actions(会出现在「更多视图」菜单里,虽然不显眼但能用)。
14
+ var SLOT_FALLBACKS = ["conversation.session.header.actions", "conversation.session.header.views.actions"];
15
+
16
+ /* __CANVAS_RENDERER__ */
17
+ /* __GRAPH_LAYOUT__ */
18
+
19
+ var defaultSettings = { hideButton: false, showGuideOnEmpty: true, graphTheme: "classic", graphLayout: "sphere", autoMemory: { enabled: false, maxPerTurn: 2, dailyCap: 40 }, showAutoMemoryCostHint: true };
20
+ var actionStyle = { padding: "7px 12px", borderRadius: "8px", border: "1px solid var(--memory-border,#6999b8)", background: "var(--memory-button,#102236)", color: "var(--memory-text,#e2f4ff)", font: "inherit", cursor: "pointer" };
21
+ var fieldStyle = { boxSizing: "border-box", padding: "10px 12px", borderRadius: "8px", border: "1px solid #35536b", background: "#091422", color: "#e2f4ff", font: "inherit", minWidth: 0 };
22
+ var kinds = ["知识", "印象", "对话", "事件", "日记"];
23
+ var appearanceOptions = [
24
+ { key: "graphTheme", label: "配色", choices: [["classic", "经典星空"], ["neon", "赛博霓虹"], ["paper", "铅绘素描"], ["mono", "极简单色"]] },
25
+ { key: "graphLayout", label: "布局", choices: [["sphere", "球体"], ["galaxy", "星系"], ["brain", "大脑神经网络"]] },
26
+ ];
27
+ var appearanceStyles = {
28
+ classic: {},
29
+ neon: { "--memory-text": "#f4dfff", "--memory-muted": "#ba9aca", "--memory-accent": "#ff9de1", "--memory-border": "#704580", "--memory-panel": "#170d21ee", "--memory-button": "#271333", "--memory-shadow": "none" },
30
+ paper: { "--memory-text": "#303030", "--memory-muted": "#72716d", "--memory-accent": "#404040", "--memory-border": "#b5b1a9", "--memory-panel": "#f7f5f0f2", "--memory-button": "#eeece6", "--memory-shadow": "none" },
31
+ mono: { "--memory-text": "#eeeeee", "--memory-muted": "#aaaaaa", "--memory-accent": "#eeeeee", "--memory-border": "#666666", "--memory-panel": "#171717ee", "--memory-button": "#252525", "--memory-shadow": "none" },
32
+ };
33
+
34
+ function AppearanceControls(props) {
35
+ return React.createElement("section", { "aria-label": "图谱外观", "data-memory-engine-appearance-options": props.location,
36
+ style: { color: "var(--memory-text,#dbeaf8)", font: "14px/1.7 system-ui,sans-serif" } },
37
+ appearanceOptions.map(function (group) {
38
+ return React.createElement("fieldset", { key: group.key, disabled: props.busy, style: { border: 0, margin: "0 0 14px", padding: 0 } },
39
+ React.createElement("legend", { style: { marginBottom: "8px", color: "var(--memory-muted,#9fb7ce)" } }, group.label),
40
+ React.createElement("div", { style: { display: "flex", flexWrap: "wrap", gap: "8px 14px" } }, group.choices.map(function (choice) {
41
+ return React.createElement("label", { key: choice[0], style: { display: "inline-flex", alignItems: "center", gap: "6px", cursor: props.busy ? "wait" : "pointer" } },
42
+ React.createElement("input", { type: "radio", name: "memory-" + props.location + "-" + group.key, value: choice[0],
43
+ "data-memory-engine-appearance-choice": group.key + ":" + choice[0],
44
+ checked: props.settings[group.key] === choice[0], style: { margin: 0, accentColor: "var(--memory-accent,#a4e6ff)" },
45
+ onChange: function () { var changes = {}; changes[group.key] = choice[0]; props.onChange(changes); } }), choice[1]);
46
+ })));
47
+ }), React.createElement("button", { type: "button", disabled: props.busy, style: actionStyle,
48
+ "data-memory-engine-appearance-reset": props.location,
49
+ onClick: function () { props.onChange({ graphTheme: "classic", graphLayout: "sphere" }); } }, "恢复默认"));
50
+ }
51
+
52
+ function requestApi(url, options) {
53
+ return fetch(url, Object.assign({ cache: "no-store" }, options)).then(function (response) {
54
+ return response.json().then(function (result) {
55
+ if (!response.ok || !result || result.ok !== true) throw new Error(result && result.error || "HTTP " + response.status);
56
+ if (!result.data || typeof result.data !== "object") throw new Error("接口返回格式无效");
57
+ return result.data;
58
+ });
59
+ });
60
+ }
61
+
62
+ function readApi(url, signal) { return requestApi(url, { signal: signal }); }
63
+
64
+ function loadSettings() {
65
+ var controller = new AbortController();
66
+ var timer = setTimeout(function () { controller.abort(); }, 5000);
67
+ return Promise.resolve().then(function () {
68
+ return readApi("/memory-engine/api/config", controller.signal);
69
+ }).then(function (settings) {
70
+ if (typeof settings.hideButton !== "boolean" || typeof settings.showGuideOnEmpty !== "boolean") throw new Error("按钮配置格式无效");
71
+ return settings;
72
+ }).finally(function () { clearTimeout(timer); });
73
+ }
74
+
75
+ function copyText(text) {
76
+ function fallback() {
77
+ var area = document.createElement("textarea");
78
+ var active = document.activeElement;
79
+ area.value = text;
80
+ area.style.cssText = "position:fixed;left:-9999px;top:0";
81
+ document.body.appendChild(area);
82
+ area.select();
83
+ try {
84
+ if (!document.execCommand("copy")) throw new Error("复制失败");
85
+ } finally {
86
+ area.remove();
87
+ if (active && typeof active.focus === "function") active.focus();
88
+ }
89
+ }
90
+ return Promise.resolve().then(function () {
91
+ if (navigator.clipboard && navigator.clipboard.writeText) return navigator.clipboard.writeText(text);
92
+ return fallback();
93
+ }).catch(function () { return fallback(); });
94
+ }
95
+
96
+ function GuideCard(props) {
97
+ var copiedState = React.useState("复制这段提示词");
98
+ var copyLabel = copiedState[0];
99
+ var setCopyLabel = copiedState[1];
100
+ var card = React.useRef(null);
101
+ React.useEffect(function () { if (card.current) card.current.focus(); }, []);
102
+ function trapTab(event) {
103
+ if (event.key !== "Tab" || !card.current) return;
104
+ var buttons = Array.from(card.current.querySelectorAll("button"));
105
+ var position = buttons.indexOf(document.activeElement);
106
+ if (event.shiftKey && position <= 0) { event.preventDefault(); buttons[buttons.length - 1].focus(); }
107
+ else if (!event.shiftKey && (position === -1 || position === buttons.length - 1)) { event.preventDefault(); buttons[0].focus(); }
108
+ }
109
+ var data = props.data;
110
+ return React.createElement("div", {
111
+ "data-memory-engine-guide-layer": "true",
112
+ style: { position: "absolute", inset: 0, zIndex: 2, display: "grid", placeItems: "center", padding: "16px", background: "rgba(3,6,12,.7)" },
113
+ }, React.createElement("section", {
114
+ ref: card, role: "dialog", "aria-modal": "true", "aria-label": "使用指南", tabIndex: -1,
115
+ "data-memory-engine-guide": "true", onKeyDown: trapTab,
116
+ style: { boxSizing: "border-box", width: "min(620px,100%)", maxHeight: "calc(100vh - 32px)", overflow: "auto", padding: "24px", border: "1px solid #3976a0", borderRadius: "16px", background: "#07111f", color: "#dbeaf8", font: "14px/1.65 system-ui,sans-serif", boxShadow: "0 16px 80px #0008" },
117
+ }, React.createElement("h2", { style: { margin: "0 0 16px", fontSize: "22px", color: "#b7e9ff" } }, "怎么用"),
118
+ props.error ? React.createElement("div", { role: "alert" },
119
+ React.createElement("p", null, "引导加载失败,请重试。"),
120
+ React.createElement("button", { type: "button", onClick: props.onRetry, style: actionStyle }, "重试"))
121
+ : !data ? React.createElement("p", { role: "status" }, "正在加载使用指南……")
122
+ : React.createElement(React.Fragment, null,
123
+ data.empty ? React.createElement("p", { style: { color: "#9fb7ce" } }, "还没有记忆,从下面任一步开始。") : null,
124
+ data.steps.map(function (step, index) {
125
+ return React.createElement("div", { key: index, style: { borderLeft: "3px solid #28577e", paddingLeft: "14px", marginBottom: "18px" } },
126
+ React.createElement("h3", { style: { fontSize: "15px", color: "#9fd0f5", margin: "0 0 6px" } }, "第 " + (index + 1) + " 步 " + step.title),
127
+ React.createElement("p", { style: { margin: "0 0 8px" } }, step.body),
128
+ index === 0 ? React.createElement(React.Fragment, null,
129
+ React.createElement("pre", { style: { whiteSpace: "pre-wrap", overflowWrap: "anywhere", maxHeight: "180px", overflow: "auto", margin: "8px 0", padding: "12px", background: "#030a13", borderRadius: "8px", font: "12px/1.6 ui-monospace,Consolas,monospace" } }, data.prompt),
130
+ React.createElement("button", { type: "button", "data-memory-engine-copy": "true", style: actionStyle,
131
+ onClick: function () { copyText(data.prompt).then(function () { setCopyLabel("已复制 ✓"); }, function () { setCopyLabel("复制失败,请手动复制"); }); },
132
+ }, copyLabel)) : null,
133
+ index === 2 ? React.createElement("button", { type: "button", onClick: props.onRefresh, style: actionStyle }, "刷新图谱") : null);
134
+ }),
135
+ React.createElement("p", { style: { fontSize: "12px", color: "#9fb7ce", overflowWrap: "anywhere" } }, "数据目录:", data.dataDir)),
136
+ React.createElement("div", { style: { display: "flex", justifyContent: "flex-end", marginTop: "16px" } },
137
+ React.createElement("button", { type: "button", onClick: props.onClose, style: actionStyle }, "知道了"))));
138
+ }
139
+
140
+ function MemoryDetails(props) {
141
+ var node = props.node;
142
+ var text = typeof node.text === "string" ? node.text : "";
143
+ if (node.isCore) text = Array.from(text).slice(0, 4000).join("");
144
+ var defaultCopyLabel = node.isCore ? "复制全文" : "复制正文";
145
+ var title = String(node.title || "").trim() || Array.from(text).slice(0, 20).join("");
146
+ var date = new Date(node.ts);
147
+ var day = Number.isFinite(date.getTime()) ? date.getFullYear() + "-" + String(date.getMonth() + 1).padStart(2, "0") + "-" + String(date.getDate()).padStart(2, "0") : "未知";
148
+ var heat = { 1: "热", 2: "温", 3: "冷" }[node.heat];
149
+ var rows = [
150
+ ["重要度", heat ? heat + "(" + node.heat + ")" : "未知"],
151
+ ["留存", Number.isFinite(node.retention) ? node.retention.toFixed(2) : "—"],
152
+ ["连接数", node.degree == null ? 0 : node.degree],
153
+ ["时间", day],
154
+ ];
155
+ var emotion = Array.isArray(node.emotion) ? node.emotion.filter(function (item) { return item && item.tag; }).map(function (item) { return item.tag; }).join(" / ") : "";
156
+ if (emotion) rows.push(["情绪", emotion]);
157
+ var copiedState = React.useState(defaultCopyLabel);
158
+ var copyLabel = copiedState[0];
159
+ var setCopyLabel = copiedState[1];
160
+ var timer = React.useRef(null);
161
+ var copyVersion = React.useRef(0);
162
+ var body = React.useRef(null);
163
+ React.useEffect(function () {
164
+ copyVersion.current++;
165
+ clearTimeout(timer.current);
166
+ timer.current = null;
167
+ setCopyLabel(defaultCopyLabel);
168
+ if (body.current) body.current.scrollTop = 0;
169
+ return function () { copyVersion.current++; clearTimeout(timer.current); timer.current = null; };
170
+ }, [node.id, text, defaultCopyLabel]);
171
+ function copyBody() {
172
+ var version = ++copyVersion.current;
173
+ clearTimeout(timer.current);
174
+ copyText(text).then(function () {
175
+ if (version !== copyVersion.current) return;
176
+ setCopyLabel("已复制 ✓");
177
+ timer.current = setTimeout(function () {
178
+ if (version === copyVersion.current) setCopyLabel(defaultCopyLabel);
179
+ timer.current = null;
180
+ }, 1800);
181
+ }, function () {
182
+ if (version === copyVersion.current) setCopyLabel("复制失败,请重试");
183
+ });
184
+ }
185
+ return React.createElement("section", {
186
+ role: "region", "aria-label": "记忆详情", "data-memory-engine-detail": "true", "data-memory-engine-node": node.id,
187
+ style: { position: "absolute", top: "72px", right: "18px", zIndex: 1, width: "min(350px,calc(100% - 36px))", maxHeight: "calc(100% - 96px)", boxSizing: "border-box", display: "flex", flexDirection: "column", overflow: "hidden", padding: "18px", border: "1px solid #3976a0", borderRadius: "14px", background: "#07111fee", color: "#dbeaf8", font: "14px/1.6 system-ui,sans-serif", boxShadow: "0 0 40px #1763a033", backdropFilter: "blur(8px)", animation: "dsh-memory-detail-in 180ms ease-out" },
188
+ }, React.createElement("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "12px", flexShrink: 0 } },
189
+ React.createElement("span", { "data-memory-engine-detail-kind": "true", style: { color: "#a4e6ff", background: "#15344b", borderRadius: "6px", padding: "2px 8px", overflowWrap: "anywhere" } }, node.kind || "未分类"),
190
+ React.createElement("button", { type: "button", "aria-label": "关闭记忆详情", onClick: props.onClose, style: { border: 0, padding: "2px 6px", background: "transparent", color: "#a4bad0", fontSize: "22px", cursor: "pointer" } }, "×")),
191
+ React.createElement("h2", { "data-memory-engine-detail-title": "true", style: { margin: "10px 0", fontSize: "18px", color: "#b7e9ff", overflowWrap: "anywhere", whiteSpace: "normal", maxHeight: "18vh", overflowY: "auto", flexShrink: 0 } }, title),
192
+ node.isCore ? null : React.createElement("dl", { style: { display: "grid", gridTemplateColumns: "64px minmax(0,1fr)", gap: "5px 10px", margin: "0 0 12px", fontSize: "12px", color: "#9fb7ce", maxHeight: "25vh", overflowY: "auto", flexShrink: 0 } },
193
+ rows.map(function (row) { return React.createElement(React.Fragment, { key: row[0] },
194
+ React.createElement("dt", { style: { margin: 0 } }, row[0]),
195
+ React.createElement("dd", { style: { margin: 0, overflowWrap: "anywhere" } }, row[1])); })),
196
+ React.createElement("div", { ref: body, "data-memory-engine-detail-text": "true", style: { minHeight: 0, flex: "1 1 auto", overflowY: "auto", whiteSpace: "pre-wrap", overflowWrap: "anywhere", borderTop: "1px solid #3976a055", borderBottom: "1px solid #3976a055", padding: "12px 0" } }, text),
197
+ React.createElement("div", { style: { display: "flex", flexWrap: "wrap", gap: "8px", marginTop: "12px", flexShrink: 0 } },
198
+ node.isCore ? null : React.createElement("button", { type: "button", "data-memory-engine-highlight": "true", style: actionStyle, onClick: props.onHighlight }, "在图里高亮它"),
199
+ React.createElement("button", { type: "button", "data-memory-engine-copy-body": "true", style: actionStyle, onClick: copyBody }, copyLabel)));
200
+ }
201
+
202
+ function MemoryCanvas(props) {
203
+ var canvas = React.useRef(null);
204
+ var appearance = React.useRef(null);
205
+ appearance.current = { graphTheme: props.graphTheme || "classic", graphLayout: props.graphLayout || "sphere" };
206
+ var dataState = React.useState(null);
207
+ var data = dataState[0];
208
+ var setData = dataState[1];
209
+ var errorState = React.useState("");
210
+ var error = errorState[0];
211
+ var setError = errorState[1];
212
+ var retryState = React.useState(0);
213
+ var retry = retryState[0];
214
+ var setRetry = retryState[1];
215
+ React.useEffect(function () {
216
+ var controller = new AbortController();
217
+ var active = true;
218
+ var renderer;
219
+ setError("");
220
+ readApi("/memory-engine/api/map", controller.signal).then(function (nextData) {
221
+ if (!active) return;
222
+ renderer = createRenderer(canvas.current, nextData, Object.assign({ frameInterval: 25, layoutNodes: layoutPositions }, appearance.current));
223
+ props.rendererRef.current = renderer;
224
+ renderer.onSelect(function (node) { props.selectionVersion.current++; props.onSelect(node); });
225
+ renderer.start();
226
+ setData(nextData);
227
+ }).catch(function (failure) {
228
+ if (!active) return;
229
+ if (renderer) renderer.destroy();
230
+ if (props.rendererRef.current === renderer) props.rendererRef.current = null;
231
+ console.error("[dsh-memory-engine] Canvas 图谱加载失败:", failure);
232
+ setError(failure.message);
233
+ });
234
+ return function () {
235
+ active = false;
236
+ controller.abort();
237
+ if (renderer) renderer.destroy();
238
+ if (props.rendererRef.current === renderer) props.rendererRef.current = null;
239
+ };
240
+ }, [retry, props.rendererRef, props.selectionVersion, props.onSelect]);
241
+ React.useEffect(function () {
242
+ if (!props.rendererRef.current) return;
243
+ props.rendererRef.current.setTheme(appearance.current.graphTheme);
244
+ props.rendererRef.current.setLayout(appearance.current.graphLayout);
245
+ }, [props.graphTheme, props.graphLayout]);
246
+ return React.createElement(React.Fragment, null,
247
+ React.createElement("canvas", {
248
+ ref: canvas, "data-memory-engine-canvas": "true", "aria-label": "记忆神经网络",
249
+ style: { display: "block", width: "100%", height: "100%", touchAction: "none", cursor: "grab" },
250
+ }),
251
+ React.createElement("div", {
252
+ "data-memory-engine-summary": "true",
253
+ style: { position: "absolute", top: "16px", left: "18px", pointerEvents: "none", color: "var(--memory-text,#d8e9f8)", font: "13px/1.65 system-ui,sans-serif", textShadow: "var(--memory-shadow,0 0 12px #3388bb88)" },
254
+ }, React.createElement("strong", { style: { color: "var(--memory-accent,#a4e6ff)", fontSize: "16px" } }, "记忆神经网络"),
255
+ React.createElement("div", null, data ? data.nodes.length + " 个神经元 · " + data.links.length + " 条突触" : "正在加载图谱……"),
256
+ React.createElement("div", { style: { color: "var(--memory-muted,#7491a8)", fontSize: "12px" } }, "大小=重要度和连接数 · 亮度=留存")),
257
+ data ? React.createElement("section", {
258
+ "aria-label": "复习清单",
259
+ style: { position: "absolute", bottom: "16px", left: "18px", width: "min(400px,calc(100% - 36px))", boxSizing: "border-box", maxHeight: "32%", overflow: "auto", padding: "15px", background: "var(--memory-panel,#07111fee)", border: "1px solid var(--memory-border,#3976a055)", borderRadius: "14px", boxShadow: "var(--memory-shadow,0 0 40px #1763a033)", backdropFilter: "blur(8px)", font: "12px/1.65 system-ui,sans-serif", color: "var(--memory-text,#b1c6d9)" },
260
+ }, React.createElement("h2", { style: { fontSize: "14px", color: "var(--memory-accent,#ffdb82)", margin: "0 0 8px" } }, "复习清单 · 留存最低的记忆"),
261
+ data.review.map(function (item, index) {
262
+ return React.createElement("div", { key: item.id || index, style: { display: "flex", gap: "7px", alignItems: "center", margin: "5px 0" } },
263
+ React.createElement("span", { style: { flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, (index + 1) + ". [" + item.kind + "] " + item.title),
264
+ React.createElement("span", { style: { width: "52px", height: "5px", borderRadius: "5px", background: "var(--memory-border,#335066)", overflow: "hidden" } },
265
+ React.createElement("i", { style: { display: "block", height: "100%", background: "var(--memory-accent,#6bc9fa)", width: Math.min(100, Math.max(0, item.retention * 100)) + "%" } })));
266
+ })) : null,
267
+ React.createElement("div", { style: { position: "absolute", right: "18px", bottom: "18px", pointerEvents: "none", color: "var(--memory-muted,#7b99ad)", font: "13px system-ui,sans-serif" } }, "拖拽旋转 · 滚轮缩放 · 点光点看关联"),
268
+ error ? React.createElement("div", { role: "alert", style: { position: "absolute", inset: "40% 20% auto", textAlign: "center", color: "#d8e9f8" } },
269
+ React.createElement("p", null, "图谱加载失败:" + error),
270
+ React.createElement("button", { type: "button", style: actionStyle, onClick: function () { setRetry(function (value) { return value + 1; }); } }, "重试")) : null);
271
+ }
272
+
273
+ function ConfirmationDialog(props) {
274
+ var valueState = React.useState("");
275
+ var value = valueState[0], setValue = valueState[1];
276
+ var errorState = React.useState("");
277
+ var error = errorState[0], setError = errorState[1];
278
+ var form = React.useRef(null);
279
+ React.useEffect(function () {
280
+ var active = document.activeElement;
281
+ var target = form.current && form.current.querySelector("input,button");
282
+ if (target) target.focus();
283
+ return function () { if (active && active.isConnected) active.focus(); };
284
+ }, []);
285
+ function onKey(event) {
286
+ if (event.key === "Escape") {
287
+ event.preventDefault(); event.stopPropagation();
288
+ if (!props.busy) props.onClose();
289
+ }
290
+ if (event.key === "Tab" && form.current) {
291
+ var controls = Array.from(form.current.querySelectorAll("input,button")).filter(function (control) { return !control.disabled; });
292
+ if (!controls.length) { event.preventDefault(); return; }
293
+ var index = controls.indexOf(document.activeElement);
294
+ if (event.shiftKey && index <= 0) { event.preventDefault(); controls[controls.length - 1].focus(); }
295
+ else if (!event.shiftKey && (index === -1 || index === controls.length - 1)) { event.preventDefault(); controls[0].focus(); }
296
+ }
297
+ }
298
+ return React.createElement("div", { "data-memory-engine-confirm": "true", onKeyDown: onKey,
299
+ style: { position: "fixed", inset: 0, zIndex: 3, display: "grid", placeItems: "center", padding: "24px", background: "#03060ccc" } },
300
+ React.createElement("form", { ref: form, role: "dialog", "aria-modal": "true", "aria-label": props.title,
301
+ onSubmit: function (event) {
302
+ event.preventDefault();
303
+ if (props.busy) return;
304
+ if (props.requireDelete && value !== "DELETE") { setError("请输入大写 DELETE,其他内容不会执行清空。"); return; }
305
+ setError(""); props.onConfirm();
306
+ },
307
+ style: { width: "min(460px,100%)", padding: "24px", border: "1px solid #8b505c", borderRadius: "16px", background: "#101a29", color: "#e2f4ff", boxShadow: "0 16px 80px #0008" } },
308
+ React.createElement("h2", { style: { margin: "0 0 12px", fontSize: "20px" } }, props.title),
309
+ React.createElement("p", { style: { color: "#c2cedc" } }, props.message),
310
+ props.requireDelete ? React.createElement("label", null, "输入 DELETE 确认",
311
+ React.createElement("input", { "aria-label": "输入 DELETE 确认", autoComplete: "off", value: value, disabled: props.busy,
312
+ onChange: function (event) { setValue(event.target.value); setError(""); }, style: Object.assign({}, fieldStyle, { display: "block", width: "100%", marginTop: "8px" }) })) : null,
313
+ error || props.error ? React.createElement("p", { role: "alert", style: { color: "#ffb8b8" } }, error || props.error) : null,
314
+ React.createElement("div", { style: { display: "flex", justifyContent: "flex-end", gap: "10px", marginTop: "24px" } },
315
+ React.createElement("button", { type: "button", disabled: props.busy, onClick: props.onClose, style: actionStyle }, "取消"),
316
+ React.createElement("button", { type: "submit", disabled: props.busy, style: Object.assign({}, actionStyle, { borderColor: "#c86b7a", background: "#572a38" }) }, props.busy ? "正在处理…" : props.submitLabel))));
317
+ }
318
+
319
+ function MemoryRow(props) {
320
+ var item = props.item;
321
+ var expandedState = React.useState(false);
322
+ var expanded = expandedState[0], setExpanded = expandedState[1];
323
+ var detailState = React.useState(item);
324
+ var detail = detailState[0], setDetail = detailState[1];
325
+ var editState = React.useState(null);
326
+ var edit = editState[0], setEdit = editState[1];
327
+ var confirmState = React.useState(false);
328
+ var confirming = confirmState[0], setConfirming = confirmState[1];
329
+ var errorState = React.useState("");
330
+ var error = errorState[0], setError = errorState[1];
331
+ var loadingState = React.useState(false);
332
+ var loading = loadingState[0], setLoading = loadingState[1];
333
+ var busyState = React.useState(false);
334
+ var busy = busyState[0], setBusy = busyState[1];
335
+ var mutation = React.useRef(null);
336
+ React.useEffect(function () { return function () { if (mutation.current) mutation.current.abort(); }; }, []);
337
+ React.useEffect(function () {
338
+ if (!expanded) return;
339
+ var controller = new AbortController();
340
+ setLoading(true); setError("");
341
+ readApi("/memory-engine/api/item/" + encodeURIComponent(item.id), controller.signal).then(function (result) {
342
+ if (!controller.signal.aborted) setDetail(result);
343
+ }).catch(function (failure) {
344
+ if (!controller.signal.aborted) setError("读取详情失败:" + failure.message);
345
+ }).finally(function () { if (!controller.signal.aborted) setLoading(false); });
346
+ return function () { controller.abort(); };
347
+ }, [expanded, item.id]);
348
+ function mutate(method) {
349
+ if (mutation.current) return;
350
+ var controller = new AbortController();
351
+ mutation.current = controller;
352
+ setBusy(true); setError("");
353
+ var options = { method: method, signal: controller.signal };
354
+ if (method === "PUT") { options.headers = { "Content-Type": "application/json" }; options.body = JSON.stringify(edit); }
355
+ requestApi("/memory-engine/api/item/" + encodeURIComponent(item.id), options).then(function () {
356
+ if (controller.signal.aborted) return;
357
+ setEdit(null); setExpanded(false); setConfirming(false); props.onChanged();
358
+ }).catch(function (failure) {
359
+ if (!controller.signal.aborted) setError((method === "PUT" ? "保存失败:" : "删除失败:") + failure.message);
360
+ }).finally(function () {
361
+ if (!controller.signal.aborted) { mutation.current = null; setBusy(false); }
362
+ });
363
+ }
364
+ function change(field, value) { setEdit(function (previous) { var next = Object.assign({}, previous); next[field] = value; return next; }); }
365
+ var choices = kinds.includes(detail.kind) ? kinds : [detail.kind].concat(kinds);
366
+ return React.createElement("article", { "data-memory-engine-row": item.id,
367
+ style: { marginBottom: "12px", border: "1px solid " + (expanded ? "#4c85a4" : "#263f54"), borderRadius: "12px", background: "#0b1624", overflowWrap: "anywhere" } },
368
+ React.createElement("button", { type: "button", "data-memory-engine-expand": item.id, "aria-expanded": expanded, disabled: busy,
369
+ onClick: function () { setExpanded(!expanded); setEdit(null); setError(""); },
370
+ style: { width: "100%", display: "block", padding: "18px 20px", border: 0, borderRadius: "12px", background: "transparent", color: "inherit", textAlign: "left", font: "inherit", cursor: "pointer" } },
371
+ React.createElement("span", { style: { display: "flex", alignItems: "center", gap: "12px", flexWrap: "wrap" } },
372
+ React.createElement("span", { style: { color: "#a4e6ff", background: "#15344b", padding: "2px 8px", borderRadius: "6px", flexShrink: 0 } }, item.kind),
373
+ React.createElement("strong", { style: { flex: "1 1 180px", overflowWrap: "anywhere" } }, item.title),
374
+ React.createElement("span", { style: { color: "#8db0c7", fontSize: "12px", whiteSpace: "nowrap" } }, "留存 " + item.retention.toFixed(2) + " · " + item.degree + " 个连接")),
375
+ !expanded ? React.createElement("span", { style: { display: "block", color: "#9fb7ce", marginTop: "8px", overflowWrap: "anywhere" } }, item.preview) : null),
376
+ expanded ? React.createElement("div", { style: { padding: "0 20px 18px" } },
377
+ loading ? React.createElement("p", { role: "status" }, "正在读取全文…") : null,
378
+ edit ? React.createElement("form", { "data-memory-engine-editor": item.id, onSubmit: function (event) { event.preventDefault(); mutate("PUT"); } },
379
+ React.createElement("textarea", { "aria-label": "记忆正文", value: edit.text, disabled: busy,
380
+ onChange: function (event) { change("text", event.target.value); },
381
+ style: Object.assign({}, fieldStyle, { width: "100%", minHeight: "150px", maxHeight: "40vh", resize: "vertical", lineHeight: 1.7 }) }),
382
+ React.createElement("div", { style: { display: "flex", gap: "12px", flexWrap: "wrap", margin: "12px 0" } },
383
+ React.createElement("label", null, "分类 ", React.createElement("select", { "aria-label": "编辑分类", value: edit.kind, disabled: busy, style: fieldStyle,
384
+ onChange: function (event) { change("kind", event.target.value); } }, choices.map(function (value) { return React.createElement("option", { key: value, value: value }, value); }))),
385
+ React.createElement("label", null, "重要度 ", React.createElement("select", { "aria-label": "编辑重要度", value: edit.heat, disabled: busy, style: fieldStyle,
386
+ onChange: function (event) { change("heat", Number(event.target.value)); } },
387
+ [[1, "热(1)"], [2, "温(2)"], [3, "冷(3)"]].map(function (entry) { return React.createElement("option", { key: entry[0], value: entry[0] }, entry[1]); }))),
388
+ React.createElement("label", null, "相关人 ", React.createElement("input", { "aria-label": "编辑相关人", value: edit.who, disabled: busy,
389
+ onChange: function (event) { change("who", event.target.value); }, style: Object.assign({}, fieldStyle, { width: "140px" }) }))),
390
+ React.createElement("div", { style: { display: "flex", gap: "8px" } },
391
+ React.createElement("button", { type: "submit", disabled: busy, "data-memory-engine-save": "true", style: actionStyle }, busy ? "正在保存…" : "保存"),
392
+ React.createElement("button", { type: "button", disabled: busy, "data-memory-engine-cancel-edit": "true", style: actionStyle,
393
+ onClick: function () { setEdit(null); setExpanded(false); setError(""); } }, "取消")))
394
+ : React.createElement(React.Fragment, null,
395
+ React.createElement("div", { "data-memory-engine-full-text": "true", style: { whiteSpace: "pre-wrap", maxHeight: "40vh", overflowY: "auto", color: "#c2d3e3", marginBottom: "14px" } }, detail.text),
396
+ React.createElement("div", { style: { display: "flex", gap: "8px" } },
397
+ React.createElement("button", { type: "button", "data-memory-engine-edit": item.id, disabled: loading || busy, style: actionStyle,
398
+ onClick: function () { setError(""); setEdit({ text: detail.text, kind: detail.kind, heat: detail.heat, who: detail.who || "" }); } }, "编辑"),
399
+ React.createElement("button", { type: "button", "data-memory-engine-delete": item.id, disabled: loading || busy,
400
+ onClick: function () { setError(""); setConfirming(true); }, style: Object.assign({}, actionStyle, { color: "#ffb8b8", borderColor: "#8b505c" }) }, "删除"))),
401
+ error ? React.createElement("p", { role: "alert", style: { color: "#ffb8b8" } }, error) : null) : null,
402
+ confirming ? React.createElement(ConfirmationDialog, { title: "删除这条记忆", message: "确定删除这条记忆?不可撤销。操作前会保留备份。", submitLabel: "确定删除",
403
+ busy: busy, error: error, onClose: function () { setConfirming(false); }, onConfirm: function () { mutate("DELETE"); } }) : null);
404
+ }
405
+
406
+ function MemoryList(props) {
407
+ var searchState = React.useState("");
408
+ var search = searchState[0], setSearch = searchState[1];
409
+ var queryState = React.useState("");
410
+ var query = queryState[0], setQuery = queryState[1];
411
+ var kindState = React.useState("");
412
+ var kind = kindState[0], setKind = kindState[1];
413
+ var sortState = React.useState("recent");
414
+ var sort = sortState[0], setSort = sortState[1];
415
+ var offsetState = React.useState(0);
416
+ var offset = offsetState[0], setOffset = offsetState[1];
417
+ var pageState = React.useState({ items: [], total: 0, allTotal: 0 });
418
+ var page = pageState[0], setPage = pageState[1];
419
+ var loadingState = React.useState(true);
420
+ var loading = loadingState[0], setLoading = loadingState[1];
421
+ var errorState = React.useState("");
422
+ var error = errorState[0], setError = errorState[1];
423
+ var retryState = React.useState(0);
424
+ var retry = retryState[0], setRetry = retryState[1];
425
+ function refresh() {
426
+ setOffset(0); setRetry(function (value) { return value + 1; });
427
+ if (props.onChanged) props.onChanged();
428
+ }
429
+ React.useEffect(function () {
430
+ if (search === query) return;
431
+ var timer = setTimeout(function () { setOffset(0); setQuery(search); }, 250);
432
+ return function () { clearTimeout(timer); };
433
+ }, [search]);
434
+ React.useEffect(function () {
435
+ var controller = new AbortController();
436
+ var active = true;
437
+ setLoading(true); setError("");
438
+ var params = new URLSearchParams({ q: query, kind: kind, sort: sort, limit: "50", offset: String(offset) });
439
+ readApi("/memory-engine/api/list?" + params, controller.signal).then(function (result) {
440
+ if (!active) return;
441
+ setPage(function (previous) { return Object.assign({}, result, { items: offset === 0 ? result.items : previous.items.concat(result.items) }); });
442
+ }).catch(function (failure) {
443
+ if (active) setError("加载记忆失败:" + failure.message);
444
+ }).finally(function () { if (active) setLoading(false); });
445
+ return function () { active = false; controller.abort(); };
446
+ }, [query, kind, sort, offset, retry]);
447
+ return React.createElement("section", {
448
+ "aria-label": "记忆列表", "data-memory-engine-list": "true",
449
+ style: { position: "absolute", inset: "76px 0 0", display: "flex", flexDirection: "column", color: "#dbeaf8", font: "14px/1.6 system-ui,sans-serif" },
450
+ }, React.createElement("div", { style: { width: "min(980px,100%)", margin: "0 auto", padding: "0 24px 16px", display: "flex", gap: "12px", flexWrap: "wrap" } },
451
+ React.createElement("input", { type: "search", "aria-label": "搜索记忆", placeholder: "搜索正文或标题…", value: search,
452
+ onChange: function (event) { setSearch(event.target.value); }, style: Object.assign({}, fieldStyle, { flex: "1 1 220px" }) }),
453
+ React.createElement("select", { "aria-label": "分类筛选", value: kind, style: fieldStyle,
454
+ onChange: function (event) { setOffset(0); setKind(event.target.value); } },
455
+ [""].concat(kinds).map(function (value) { return React.createElement("option", { key: value, value: value }, value || "全部分类"); })),
456
+ React.createElement("select", { "aria-label": "记忆排序", value: sort, style: fieldStyle,
457
+ onChange: function (event) { setOffset(0); setSort(event.target.value); } },
458
+ [["recent", "最近"], ["oldest", "最老"], ["fading", "快忘了"], ["degree", "连接最多"]].map(function (entry) { return React.createElement("option", { key: entry[0], value: entry[0] }, entry[1]); }))),
459
+ React.createElement("div", { "data-memory-engine-list-scroll": "true", style: { flex: 1, minHeight: 0, overflowY: "auto", padding: "0 24px 24px" } },
460
+ React.createElement("div", { style: { maxWidth: "932px", margin: "0 auto" }, "aria-busy": loading },
461
+ error ? React.createElement("div", { role: "alert", style: { color: "#ffb8b8", padding: "16px" } }, error,
462
+ React.createElement("button", { type: "button", style: actionStyle, onClick: function () { setRetry(function (value) { return value + 1; }); } }, "重试")) : null,
463
+ page.items.map(function (item) {
464
+ return React.createElement(MemoryRow, { key: item.id, item: item, onChanged: refresh });
465
+ }),
466
+ !loading && !error && !page.items.length ? React.createElement("p", { role: "status", style: { padding: "24px", textAlign: "center", color: "#9fb7ce" } }, page.allTotal ? "没有匹配的记忆,试试其他关键词或分类。" : "还没有记忆,可以点右上角 ? 查看导入方法。") : null)),
467
+ React.createElement("div", { "data-memory-engine-list-total": "true", style: { padding: "14px", textAlign: "center", color: "#9fb7ce", flexShrink: 0, borderTop: "1px solid #263f54" } },
468
+ "共 " + page.total + " 条 · 已显示 " + page.items.length + " 条",
469
+ loading ? React.createElement("span", { role: "status" }, " · 正在加载…") : null,
470
+ page.items.length < page.total ? React.createElement("button", { type: "button", "data-memory-engine-more": "true", disabled: loading || search !== query || !!error,
471
+ onClick: function () { setOffset(page.items.length); }, style: Object.assign({}, actionStyle, { marginLeft: "12px", opacity: loading ? 0.5 : 1 }) }, "加载更多") : null));
472
+ }
473
+
474
+ function AutoMemoryToggle(props) {
475
+ var enabled = props.settings.autoMemory && props.settings.autoMemory.enabled === true;
476
+ var maximum = props.settings.autoMemory && props.settings.autoMemory.maxPerTurn || 2;
477
+ return React.createElement("button", {
478
+ type: "button", role: "switch", "aria-checked": enabled, "aria-label": "自动记忆",
479
+ "data-memory-engine-auto-memory": props.location, disabled: props.busy, onClick: props.onToggle,
480
+ title: enabled ? "当前:开(每轮对话后会抽 0~" + maximum + " 条记忆)" : "当前:关(不会自动调用模型抽取记忆)",
481
+ style: Object.assign({}, actionStyle, { color: enabled ? "#e2f4ff" : "#8195aa", borderColor: enabled ? "#64bca0" : "#35536b", opacity: props.busy ? 0.6 : 1 }),
482
+ }, React.createElement("span", { "aria-hidden": "true", style: { color: enabled ? "#89f0bb" : "#8195aa", textShadow: enabled ? "0 0 8px #56d890" : "none" } }, enabled ? "●" : "○"), " 自动记忆");
483
+ }
484
+
485
+ function MemorySettings(props) {
486
+ var statsState = React.useState(null);
487
+ var stats = statsState[0], setStats = statsState[1];
488
+ var errorState = React.useState("");
489
+ var error = errorState[0], setError = errorState[1];
490
+ var noticeState = React.useState("");
491
+ var notice = noticeState[0], setNotice = noticeState[1];
492
+ var retryState = React.useState(0);
493
+ var retry = retryState[0], setRetry = retryState[1];
494
+ var busyState = React.useState(false);
495
+ var busy = busyState[0], setBusy = busyState[1];
496
+ var confirmState = React.useState(false);
497
+ var confirming = confirmState[0], setConfirming = confirmState[1];
498
+ var mutation = React.useRef(null);
499
+ var download = React.useRef(null);
500
+ var timer = React.useRef(null);
501
+ var active = React.useRef(true);
502
+ React.useEffect(function () {
503
+ active.current = true;
504
+ return function () {
505
+ active.current = false;
506
+ if (mutation.current) mutation.current.abort();
507
+ clearTimeout(timer.current);
508
+ if (download.current) URL.revokeObjectURL(download.current);
509
+ };
510
+ }, []);
511
+ React.useEffect(function () {
512
+ var controller = new AbortController();
513
+ setError("");
514
+ readApi("/memory-engine/api/stats", controller.signal).then(function (result) {
515
+ if (!controller.signal.aborted) setStats(result);
516
+ }).catch(function (failure) {
517
+ if (!controller.signal.aborted) setError("读取设置失败:" + failure.message);
518
+ });
519
+ return function () { controller.abort(); };
520
+ }, [retry]);
521
+ function manage(clearing) {
522
+ if (mutation.current) return;
523
+ var controller = new AbortController();
524
+ mutation.current = controller; setBusy(true); setError(""); setNotice("");
525
+ var options = { signal: controller.signal };
526
+ if (clearing) { options.method = "POST"; options.headers = { "Content-Type": "application/json" }; options.body = JSON.stringify({ confirm: "DELETE" }); }
527
+ requestApi("/memory-engine/api/" + (clearing ? "clear" : "export"), options).then(function (result) {
528
+ if (controller.signal.aborted) return;
529
+ if (clearing) { props.onCleared(); return; }
530
+ if (download.current) URL.revokeObjectURL(download.current);
531
+ clearTimeout(timer.current);
532
+ download.current = URL.createObjectURL(new Blob([JSON.stringify({ ok: true, data: result }, null, 2) + "\n"], { type: "application/json;charset=utf-8" }));
533
+ var anchor = document.createElement("a");
534
+ var date = new Date();
535
+ anchor.href = download.current;
536
+ anchor.download = "memories-" + date.getFullYear() + "-" + String(date.getMonth() + 1).padStart(2, "0") + "-" + String(date.getDate()).padStart(2, "0") + ".json";
537
+ document.body.appendChild(anchor); anchor.click(); anchor.remove();
538
+ timer.current = setTimeout(function () { URL.revokeObjectURL(download.current); download.current = null; timer.current = null; }, 1000);
539
+ setNotice("已开始下载,共 " + result.total + " 条记忆。");
540
+ }).catch(function (failure) {
541
+ if (!controller.signal.aborted) setError((clearing ? "清空失败:" : "导出失败:") + failure.message);
542
+ }).finally(function () {
543
+ if (!controller.signal.aborted) { mutation.current = null; setBusy(false); }
544
+ });
545
+ }
546
+ var directory = stats ? stats.file.replace(/[\\/][^\\/]*$/, "") : "";
547
+ return React.createElement("section", { "aria-label": "记忆设置", "data-memory-engine-settings": "true",
548
+ style: { position: "absolute", inset: "76px 0 0", overflowY: "auto", padding: "0 24px 32px", color: "#dbeaf8", font: "14px/1.7 system-ui,sans-serif" } },
549
+ React.createElement("div", { style: { maxWidth: "900px", margin: "0 auto", padding: "28px", border: "1px solid #263f54", borderRadius: "14px", background: "#0b1624" } },
550
+ props.settings.autoImport?.hint === "no-session-matched" ? React.createElement("div", { role: "alert", "data-memory-engine-import-hint": "true",
551
+ style: { marginBottom: "20px", padding: "12px 16px", border: "1px solid #9c7243", borderRadius: "8px", background: "#2a2118", color: "#ffe1ac" } },
552
+ React.createElement("span", null, props.settings.autoImport.message), " ",
553
+ React.createElement("button", { type: "button", "data-memory-engine-use-auto-import": "true", disabled: props.configBusy,
554
+ onClick: props.onUseAutoImport, style: actionStyle }, "改用宽松模式")) : null,
555
+ React.createElement("section", { style: { marginBottom: "24px", padding: "20px", border: "1px solid var(--memory-border,#263f54)", borderRadius: "10px", background: "var(--memory-panel,#0b1624)" } },
556
+ React.createElement("h2", { style: { margin: "0 0 12px", fontSize: "20px", color: "var(--memory-accent,#b7e9ff)" } }, "图谱外观"),
557
+ React.createElement(AppearanceControls, { settings: props.settings, busy: props.configBusy, onChange: props.onAppearanceChange, location: "settings" })),
558
+ React.createElement("section", { style: { marginBottom: "24px", paddingBottom: "20px", borderBottom: "1px solid #263f54" } },
559
+ React.createElement("h2", { style: { margin: "0 0 12px", fontSize: "20px", color: "#b7e9ff" } }, "自动记忆"),
560
+ React.createElement(AutoMemoryToggle, { settings: props.settings, busy: props.configBusy, onToggle: props.onToggle, location: "settings" }),
561
+ React.createElement("p", { style: { color: "#9fb7ce", marginBottom: 0 } }, "默认关闭。开启后每轮对话会额外调用一次模型,抽取值得记住的事实;通常最多 2 条,可随时关闭。大约每轮几分钱,实际费用以模型及计费方案为准。自动导入是独立设置,不受此开关控制。")),
562
+ React.createElement("h2", { style: { margin: "0 0 20px", fontSize: "20px", color: "#b7e9ff" } }, "数据管理"),
563
+ stats ? React.createElement(React.Fragment, null,
564
+ React.createElement("div", { style: { display: "flex", alignItems: "center", flexWrap: "wrap", gap: "12px" } },
565
+ React.createElement("span", { style: { flex: "1 1 360px", overflowWrap: "anywhere" } }, "数据目录:", React.createElement("code", { "data-memory-engine-directory": "true" }, directory)),
566
+ React.createElement("button", { type: "button", "data-memory-engine-copy-path": "true", style: actionStyle,
567
+ onClick: function () {
568
+ setNotice(""); setError("");
569
+ copyText(directory).then(function () { if (active.current) setNotice("数据目录已复制 ✓"); }, function () { if (active.current) setError("复制路径失败,请手动复制上方目录。"); });
570
+ } }, "复制路径")),
571
+ React.createElement("p", { "data-memory-engine-storage-stats": "true", style: { color: "#9fb7ce", margin: "16px 0 24px" } }, "记忆条数:" + stats.total + "  文件大小:" + (stats.bytes / 1024).toFixed(1) + " KB"))
572
+ : React.createElement("p", { role: "status" }, error ? "暂时无法读取数据目录。" : "正在读取数据目录…"),
573
+ error ? React.createElement("div", { role: "alert", style: { color: "#ffb8b8", marginBottom: "16px" } }, error,
574
+ !stats ? React.createElement("button", { type: "button", style: actionStyle, onClick: function () { setRetry(function (value) { return value + 1; }); } }, "重试") : null) : null,
575
+ notice ? React.createElement("p", { role: "status", style: { color: "#9fe5c4" } }, notice) : null,
576
+ React.createElement("div", { style: { padding: "24px 0", borderTop: "1px solid #263f54", borderBottom: "1px solid #263f54" } },
577
+ React.createElement("button", { type: "button", "data-memory-engine-export": "true", disabled: busy, onClick: function () { manage(false); }, style: actionStyle }, "导出全部记忆(JSON)"),
578
+ React.createElement("p", { style: { margin: "10px 0 0", fontSize: "12px", color: "#9fb7ce" } }, "下载包含全部原始条目的 JSON 文件。导入已有记忆请使用 memory_import 工具。")),
579
+ React.createElement("h3", { style: { color: "#ffb8b8", marginTop: "24px" } }, "危险操作"),
580
+ React.createElement("p", { style: { color: "#9fb7ce" } }, "清空前会自动备份,最近五份操作备份保留在数据目录。"),
581
+ React.createElement("button", { type: "button", "data-memory-engine-clear": "true", disabled: busy,
582
+ onClick: function () { setError(""); setNotice(""); setConfirming(true); }, style: Object.assign({}, actionStyle, { color: "#ffb8b8", borderColor: "#8b505c", background: "#341c28" }) }, "清空全部记忆")),
583
+ confirming ? React.createElement(ConfirmationDialog, { title: "清空全部记忆", message: "这会删除当前记忆库的全部条目。请确认已导出需要保留的内容。", requireDelete: true,
584
+ submitLabel: "确认清空", busy: busy, error: error, onClose: function () { setConfirming(false); }, onConfirm: function () { manage(true); } }) : null);
585
+ }
586
+
587
+ function MemoryButton(props) {
588
+ var state = React.useState(false);
589
+ var open = state[0];
590
+ var setOpen = state[1];
591
+ var settingsState = React.useState(Object.assign({}, defaultSettings, props.settings));
592
+ var settings = settingsState[0], setSettings = settingsState[1];
593
+ var configBusyState = React.useState(false);
594
+ var configBusy = configBusyState[0], setConfigBusy = configBusyState[1];
595
+ var configErrorState = React.useState("");
596
+ var configError = configErrorState[0], setConfigError = configErrorState[1];
597
+ var costHintState = React.useState(false);
598
+ var costHint = costHintState[0], setCostHint = costHintState[1];
599
+ var configRequest = React.useRef(null);
600
+ var guideState = React.useState(false);
601
+ var guideOpen = guideState[0];
602
+ var setGuideOpen = guideState[1];
603
+ var dataState = React.useState(null);
604
+ var guideData = dataState[0];
605
+ var setGuideData = dataState[1];
606
+ var errorState = React.useState("");
607
+ var guideError = errorState[0];
608
+ var setGuideError = errorState[1];
609
+ var retryState = React.useState(0);
610
+ var retry = retryState[0];
611
+ var setRetry = retryState[1];
612
+ var selectedState = React.useState(null);
613
+ var selected = selectedState[0];
614
+ var setSelected = selectedState[1];
615
+ var tabState = React.useState("map");
616
+ var tab = tabState[0], setTab = tabState[1];
617
+ var renderer = React.useRef(null);
618
+ var selectionVersion = React.useRef(0);
619
+ var pointer = React.useRef(null);
620
+ var help = React.useRef(null);
621
+ var dismissed = React.useRef(false);
622
+ var appearanceState = React.useState(false);
623
+ var appearanceOpen = appearanceState[0], setAppearanceOpen = appearanceState[1];
624
+ var appearancePanel = React.useRef(null);
625
+ var appearanceButton = React.useRef(null);
626
+ React.useEffect(function () {
627
+ if (!open || !appearanceOpen) return undefined;
628
+ var first = appearancePanel.current && appearancePanel.current.querySelector("input:checked");
629
+ if (first) first.focus();
630
+ function outside(event) {
631
+ if (appearancePanel.current && !appearancePanel.current.contains(event.target)
632
+ && appearanceButton.current && !appearanceButton.current.contains(event.target)) setAppearanceOpen(false);
633
+ }
634
+ window.addEventListener("pointerdown", outside);
635
+ return function () { window.removeEventListener("pointerdown", outside); };
636
+ }, [open, appearanceOpen]);
637
+ React.useEffect(function () {
638
+ return function () { if (configRequest.current) configRequest.current.abort(); };
639
+ }, []);
640
+ React.useEffect(function () {
641
+ if (!open) return undefined;
642
+ var controller = new AbortController();
643
+ setConfigBusy(true);
644
+ readApi("/memory-engine/api/config", controller.signal).then(function (data) {
645
+ if (!controller.signal.aborted) { setSettings(Object.assign({}, defaultSettings, data)); setConfigError(""); }
646
+ }).catch(function (failure) {
647
+ if (!controller.signal.aborted) setConfigError("读取配置失败:" + failure.message);
648
+ }).finally(function () { if (!controller.signal.aborted) setConfigBusy(false); });
649
+ return function () { controller.abort(); };
650
+ }, [open]);
651
+ function saveAppearance(changes) {
652
+ if (configBusy || configRequest.current) return;
653
+ if (Object.keys(changes).every(function (key) { return settings[key] === changes[key]; })) return;
654
+ var previous = settings;
655
+ var controller = new AbortController();
656
+ var timer = setTimeout(function () { controller.abort(); }, 10000);
657
+ configRequest.current = controller;
658
+ setConfigBusy(true); setConfigError("");
659
+ setSettings(Object.assign({}, settings, changes));
660
+ requestApi("/memory-engine/api/config", { method: "PUT", headers: { "Content-Type": "application/json" },
661
+ body: JSON.stringify(changes), signal: controller.signal }).then(function (data) {
662
+ if (!controller.signal.aborted) setSettings(Object.assign({}, defaultSettings, data));
663
+ }).catch(function (failure) {
664
+ setSettings(previous);
665
+ setConfigError("外观保存失败,已恢复原设置:" + (controller.signal.aborted ? "请求超时或已中断,请刷新确认状态" : failure.message));
666
+ }).finally(function () { clearTimeout(timer); configRequest.current = null; setConfigBusy(false); });
667
+ }
668
+ function saveAutoMemory(enabled) {
669
+ if (configRequest.current) return;
670
+ var previous = settings;
671
+ var controller = new AbortController();
672
+ var timer = setTimeout(function () { controller.abort(); }, 10000);
673
+ configRequest.current = controller;
674
+ setConfigBusy(true); setConfigError(""); setCostHint(false);
675
+ setSettings(Object.assign({}, settings, { autoMemory: Object.assign({}, settings.autoMemory, { enabled: enabled }) }));
676
+ requestApi("/memory-engine/api/config", { method: "PUT", headers: { "Content-Type": "application/json" },
677
+ body: JSON.stringify({ autoMemory: { enabled: enabled } }), signal: controller.signal }).then(function (data) {
678
+ setSettings(Object.assign({}, defaultSettings, data));
679
+ }).catch(function (failure) {
680
+ setSettings(previous);
681
+ setConfigError("保存失败:" + (controller.signal.aborted ? "请求超时或已中断,请刷新确认状态" : failure.message));
682
+ }).finally(function () { clearTimeout(timer); configRequest.current = null; setConfigBusy(false); });
683
+ }
684
+ function toggleAutoMemory() {
685
+ if (configBusy) return;
686
+ var enabled = settings.autoMemory && settings.autoMemory.enabled === true;
687
+ setConfigError("");
688
+ if (!enabled && settings.showAutoMemoryCostHint !== false) setCostHint(true);
689
+ else saveAutoMemory(!enabled);
690
+ }
691
+ function useAutoImport() {
692
+ if (configBusy || configRequest.current) return;
693
+ var controller = new AbortController();
694
+ var timer = setTimeout(function () { controller.abort(); }, 10000);
695
+ configRequest.current = controller;
696
+ setConfigBusy(true); setConfigError("");
697
+ requestApi("/memory-engine/api/config", { method: "PUT", headers: { "Content-Type": "application/json" },
698
+ body: JSON.stringify({ autoImport: { sessionFilter: { mode: "auto" } } }), signal: controller.signal }).then(function (data) {
699
+ setSettings(Object.assign({}, defaultSettings, data));
700
+ }).catch(function (failure) {
701
+ setConfigError("保存失败:" + (controller.signal.aborted ? "请求超时或已中断,请刷新确认状态" : failure.message));
702
+ }).finally(function () { clearTimeout(timer); configRequest.current = null; setConfigBusy(false); });
703
+ }
704
+ function closeGuide() {
705
+ dismissed.current = true;
706
+ setGuideOpen(false);
707
+ if (help.current) help.current.focus();
708
+ }
709
+ function openMap() {
710
+ dismissed.current = false;
711
+ setGuideOpen(false);
712
+ setSelected(null);
713
+ setAppearanceOpen(false);
714
+ setTab("map");
715
+ setOpen(Date.now());
716
+ }
717
+ function closeMap() { setSelected(null); setCostHint(false); setAppearanceOpen(false); setOpen(false); }
718
+ function startPointer(event) {
719
+ if (event.target.closest("[data-memory-engine-detail], [data-memory-engine-guide-layer], [data-memory-engine-controls], [data-memory-engine-appearance-panel]")) { pointer.current = null; return; }
720
+ pointer.current = { x: event.clientX, y: event.clientY, moved: 0, version: selectionVersion.current };
721
+ }
722
+ function movePointer(event) {
723
+ if (!pointer.current) return;
724
+ pointer.current.moved += Math.abs(event.clientX - pointer.current.x) + Math.abs(event.clientY - pointer.current.y);
725
+ pointer.current.x = event.clientX; pointer.current.y = event.clientY;
726
+ }
727
+ function dismissOutside() {
728
+ var gesture = pointer.current;
729
+ pointer.current = null;
730
+ if (gesture && gesture.moved < 6 && gesture.version === selectionVersion.current) setSelected(null);
731
+ }
732
+ React.useEffect(function () {
733
+ if (!open) return undefined;
734
+ var controller = new AbortController();
735
+ var active = true;
736
+ setGuideData(null);
737
+ setGuideError("");
738
+ readApi("/memory-engine/api/onboard", controller.signal).then(function (data) {
739
+ if (typeof data.empty !== "boolean" || typeof data.prompt !== "string" || typeof data.dataDir !== "string"
740
+ || !Array.isArray(data.steps) || data.steps.length !== 3
741
+ || data.steps.some(function (step) { return !step || typeof step.title !== "string" || typeof step.body !== "string"; })) throw new Error("引导数据格式无效");
742
+ if (!active) return;
743
+ setGuideData(data);
744
+ if (data.empty && settings.showGuideOnEmpty && !dismissed.current) setGuideOpen(true);
745
+ }).catch(function (error) {
746
+ if (active) setGuideError(error.message);
747
+ });
748
+ return function () { active = false; controller.abort(); };
749
+ }, [open, retry, settings.showGuideOnEmpty]);
750
+ React.useEffect(function () {
751
+ if (!open) return undefined;
752
+ function onEscape() {
753
+ if (guideOpen) closeGuide();
754
+ else if (appearanceOpen) { setAppearanceOpen(false); if (appearanceButton.current) appearanceButton.current.focus(); }
755
+ else if (selected) setSelected(null);
756
+ else closeMap();
757
+ }
758
+ function onKey(event) {
759
+ if (event.key !== "Escape") return;
760
+ if (event.target && event.target.closest && event.target.closest("[data-memory-engine-confirm]")) return;
761
+ event.preventDefault();
762
+ event.stopPropagation();
763
+ if (!event.repeat) onEscape();
764
+ }
765
+ window.addEventListener("keydown", onKey, true);
766
+ return function () { window.removeEventListener("keydown", onKey, true); };
767
+ }, [open, guideOpen, selected, appearanceOpen]);
768
+ var button = React.createElement("button", {
769
+ type: "button",
770
+ onClick: openMap,
771
+ "data-memory-engine-map": "true",
772
+ "aria-label": "打开记忆图谱",
773
+ title: "记忆图谱",
774
+ style: { display: "inline-flex", alignItems: "center", gap: "5px", padding: "5px 9px", borderRadius: "8px", border: "1px solid rgba(130,160,190,.4)", background: "transparent", color: "inherit", font: "inherit", cursor: "pointer" },
775
+ }, React.createElement("span", { "aria-hidden": "true" }, "🧠"), "记忆图谱");
776
+ if (!open) return button;
777
+ var overlay = React.createElement("div", {
778
+ role: "dialog",
779
+ "aria-modal": "true",
780
+ "aria-label": "记忆图谱",
781
+ "data-memory-engine-theme": settings.graphTheme, "data-memory-engine-layout": settings.graphLayout,
782
+ onPointerDownCapture: startPointer, onPointerMoveCapture: movePointer,
783
+ onPointerCancelCapture: function () { pointer.current = null; }, onClick: dismissOutside,
784
+ style: Object.assign({ position: "fixed", inset: 0, maxWidth: "none", maxHeight: "none", zIndex: 9999, background: "#03060c", colorScheme: settings.graphTheme === "paper" ? "light" : "dark" }, appearanceStyles[settings.graphTheme]),
785
+ }, tab === "map" ? React.createElement(MemoryCanvas, { key: open, rendererRef: renderer, selectionVersion: selectionVersion, onSelect: setSelected,
786
+ graphTheme: settings.graphTheme, graphLayout: settings.graphLayout })
787
+ : tab === "memories" ? React.createElement(MemoryList, { onChanged: function () { setRetry(function (value) { return value + 1; }); } })
788
+ : React.createElement(MemorySettings, { settings: settings, configBusy: configBusy, onToggle: toggleAutoMemory, onUseAutoImport: useAutoImport,
789
+ onAppearanceChange: saveAppearance,
790
+ onCleared: function () { openMap(); setGuideOpen(true); } }),
791
+ tab !== "map" ? React.createElement("strong", { style: { position: "absolute", top: "23px", left: "24px", font: "600 18px system-ui,sans-serif", color: "#b7e9ff" } }, "记忆管理") : null,
792
+ React.createElement("style", null, "@keyframes dsh-memory-detail-in{from{opacity:0;transform:translateX(24px)}to{opacity:1;transform:translateX(0)}}@media(prefers-reduced-motion:reduce){[data-memory-engine-detail]{animation:none!important}}"),
793
+ React.createElement("div", {
794
+ "data-memory-engine-controls": "true",
795
+ style: { position: "absolute", top: "16px", right: "18px", zIndex: 1, display: "flex", gap: "8px" },
796
+ }, React.createElement("div", { role: "tablist", "aria-label": "记忆面板", style: { display: "flex", gap: "4px", marginRight: "8px" } },
797
+ [["map", "图谱"], ["memories", "记忆"], ["settings", "设置"]].map(function (entry) {
798
+ return React.createElement("button", { key: entry[0], type: "button", role: "tab", "aria-selected": tab === entry[0], "data-memory-engine-tab": entry[0],
799
+ onClick: function () { setSelected(null); setAppearanceOpen(false); setTab(entry[0]); },
800
+ style: Object.assign({}, actionStyle, { background: tab === entry[0] ? "var(--memory-button,#204762)" : "var(--memory-panel,#091422)", borderColor: tab === entry[0] ? "var(--memory-accent,#8acded)" : "var(--memory-border,#35536b)" }) }, entry[1]);
801
+ })), React.createElement("button", { ref: appearanceButton, type: "button", "data-memory-engine-appearance": "true", "aria-expanded": appearanceOpen,
802
+ "aria-haspopup": "dialog", onClick: function () { setAppearanceOpen(!appearanceOpen); }, style: actionStyle }, "外观"),
803
+ React.createElement(AutoMemoryToggle, { settings: settings, busy: configBusy, onToggle: toggleAutoMemory, location: "header" }), React.createElement("button", {
804
+ ref: help, type: "button", onClick: function () { setGuideOpen(true); },
805
+ "aria-label": "查看使用指南", title: "使用指南", style: actionStyle,
806
+ }, "?"), React.createElement("button", {
807
+ type: "button",
808
+ onClick: closeMap,
809
+ "aria-label": "关闭记忆图谱",
810
+ style: actionStyle,
811
+ }, "关闭 ×")), appearanceOpen ? React.createElement("section", { ref: appearancePanel, role: "dialog", "aria-label": "外观", "data-memory-engine-appearance-panel": "true",
812
+ style: { position: "absolute", top: "62px", right: "18px", zIndex: 3, boxSizing: "border-box", width: "min(380px,calc(100vw - 36px))", maxHeight: "calc(100vh - 90px)", overflowY: "auto",
813
+ padding: "18px", border: "1px solid var(--memory-border,#3976a0)", borderRadius: "12px", background: "var(--memory-panel,#07111f)", boxShadow: "0 12px 35px #0004" } },
814
+ React.createElement("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "14px", color: "var(--memory-text,#dbeaf8)" } },
815
+ React.createElement("strong", null, "外观"), React.createElement("button", { type: "button", "aria-label": "关闭外观", style: actionStyle,
816
+ onClick: function () { setAppearanceOpen(false); if (appearanceButton.current) appearanceButton.current.focus(); } }, "×")),
817
+ React.createElement(AppearanceControls, { settings: settings, busy: configBusy, onChange: saveAppearance, location: "header" })) : null,
818
+ configError ? React.createElement("p", { role: "alert", "data-memory-engine-config-error": "true",
819
+ style: { position: "absolute", top: "50px", left: "18px", zIndex: 4, color: "#ff9c9c", background: "#271923", padding: "8px 12px", borderRadius: "8px", maxWidth: "min(600px,90vw)" } }, configError) : null,
820
+ costHint ? React.createElement(ConfirmationDialog, { title: "开启自动记忆", message: "自动记忆会在每轮对话后调用一次模型,大约每轮几分钱。可以随时在这里关掉。实际费用以模型及计费方案为准。",
821
+ submitLabel: "知道了,开启", busy: configBusy, onClose: function () { setCostHint(false); }, onConfirm: function () { saveAutoMemory(true); } }) : null,
822
+ selected ? React.createElement(MemoryDetails, {
823
+ node: selected, onClose: function () { setSelected(null); },
824
+ onHighlight: function () { if (renderer.current) renderer.current.fireNeuron(selected.id); },
825
+ }) : null, guideOpen ? React.createElement(GuideCard, {
826
+ data: guideData, error: guideError, onClose: closeGuide, onRefresh: openMap,
827
+ onRetry: function () { setRetry(function (value) { return value + 1; }); },
828
+ }) : null);
829
+ return React.createElement(React.Fragment, null, button, ReactDOM.createPortal(overlay, document.body));
830
+ }
831
+
832
+ function registerOn(slots, name, settings) {
833
+ var disposable = null;
834
+ slots.inject(name, function () {
835
+ try {
836
+ disposable = slots.register({
837
+ name: name,
838
+ id: "memory-engine-map",
839
+ order: 50,
840
+ label: function () { return "记忆图谱"; },
841
+ }, function ConfiguredMemoryButton() { return React.createElement(MemoryButton, { settings: settings }); });
842
+ } catch (error) {
843
+ try { console.error("[dsh-memory-engine] 注册到 " + name + " 失败:", error); } catch (ignored) {}
844
+ }
845
+ return disposable;
846
+ });
847
+ return disposable;
848
+ }
849
+
850
+ function apply(ctx) {
851
+ try {
852
+ if (!ctx || !ctx.slots || typeof ctx.slots.inject !== "function") {
853
+ try { console.warn("[dsh-memory-engine] 宿主没有 slots 服务,跳过图谱按钮(记忆工具不受影响)"); } catch (ignored) {}
854
+ return;
855
+ }
856
+ // 只注册第一个能用的插槽:优先 header.actions,老宿主没有就退到 views.actions
857
+ var target = ctx.slots && typeof ctx.slots.has === "function" && !ctx.slots.has(SLOT_FALLBACKS[0])
858
+ ? SLOT_FALLBACKS[1]
859
+ : SLOT_NAME;
860
+ function mount(settings) {
861
+ if (!settings.hideButton) registerOn(ctx.slots, target, settings);
862
+ }
863
+ function fallback(error) {
864
+ try { console.warn("[dsh-memory-engine] 配置读取失败,按默认显示按钮:", error); } catch (ignored) {}
865
+ mount(defaultSettings);
866
+ }
867
+ try { loadSettings().then(mount, fallback); }
868
+ catch (error) { fallback(error); }
869
+ } catch (error) {
870
+ try { console.error("[dsh-memory-engine] 插槽注册失败:", error); } catch (ignored) {}
871
+ }
872
+ }
873
+
874
+ exports.apply = apply;
875
+ exports.inject = ["slots"];
876
+ return module.exports;
877
+ }
878
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-memory-engine",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "DSH 本地记忆插件:自动记忆、遗忘曲线、关键词关联、可检索可编辑,数据留在本机。",
5
5
  "keywords": [
6
6
  "dsh",