@agent_memory/dsh 0.2.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/client/client.js CHANGED
@@ -172,6 +172,45 @@ ${TOKENS_CSS}
172
172
  .am-btn:hover { background: var(--am-surface-muted); border-color: var(--am-border-strong); }
173
173
  .am-btn:focus-visible { outline: 2px solid var(--am-accent); outline-offset: 2px; }
174
174
  .am-btn:disabled { opacity: .5; cursor: not-allowed; }
175
+ .am-tree-panel {
176
+ background: var(--am-surface);
177
+ border: 1px solid var(--am-border);
178
+ border-radius: 14px;
179
+ padding: 10px 10px 12px;
180
+ }
181
+ .am-tree-group { margin-bottom: 12px; }
182
+ .am-tree-group:last-child { margin-bottom: 0; }
183
+ .am-tree-group-title {
184
+ display: flex; align-items: center; justify-content: space-between;
185
+ font-size: 11px; font-weight: 650; letter-spacing: .08em; text-transform: uppercase;
186
+ color: var(--am-text-secondary);
187
+ padding: 4px 8px 6px;
188
+ }
189
+ .am-tree-row {
190
+ display: flex; gap: 8px; align-items: flex-start;
191
+ padding: 6px 8px; border-radius: 9px;
192
+ border: 1px solid transparent;
193
+ transition: background 120ms ease, border-color 120ms ease;
194
+ }
195
+ .am-tree-row:hover { background: var(--am-surface-muted); }
196
+ .am-tree-row.is-selected {
197
+ background: var(--am-accent-soft);
198
+ border-color: color-mix(in srgb, var(--am-accent) 25%, transparent);
199
+ }
200
+ .am-tree-row input[type="checkbox"] {
201
+ width: 13px;
202
+ height: 13px;
203
+ margin: 3px 0 0;
204
+ flex: none;
205
+ accent-color: var(--am-accent);
206
+ }
207
+ .am-tree-link {
208
+ flex: 1 1 auto; min-width: 0; display: block;
209
+ border: none; background: transparent; color: var(--am-text);
210
+ text-align: left; padding: 0; cursor: pointer; font: inherit;
211
+ }
212
+ .am-tree-title { font-size: 12.5px; font-weight: 550; line-height: 1.35; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
213
+ .am-tree-meta { color: var(--am-text-tertiary); font-size: 11px; margin-top: 1px; }
175
214
  .am-btn-primary {
176
215
  background: var(--am-accent); border-color: var(--am-accent); color: #fff;
177
216
  }
@@ -495,7 +534,7 @@ var MANUAL = [
495
534
  "/mem proposals 查看待审提案",
496
535
  "/mem review 逐条弹出审批卡",
497
536
  "/mem recall <关键词> 检索记忆",
498
- "/mem read <库编号> 查看全文与出处(如 F-01)",
537
+ "/mem read <库编号> 查看全文(如 F-01)",
499
538
  "/mem profile 查看你的偏好",
500
539
  "/mem export <目录> 完整备份",
501
540
  "/mem inspect 一屏概况",
@@ -539,6 +578,16 @@ function MemoryPage() {
539
578
  const [selectedSuggestions, setSelectedSuggestions] = React2.useState([]);
540
579
  const [soulPath, setSoulPath] = React2.useState(null);
541
580
  const [theme, setTheme] = React2.useState(() => initialTheme());
581
+ const [tree, setTree] = React2.useState(null);
582
+ const [treeProject, setTreeProject] = React2.useState(null);
583
+ const [focusId, setFocusId] = React2.useState(null);
584
+ const [newProjectOpen, setNewProjectOpen] = React2.useState(false);
585
+ const [newProjectName, setNewProjectName] = React2.useState("");
586
+ const [selectedIds, setSelectedIds] = React2.useState([]);
587
+ const [moveTarget, setMoveTarget] = React2.useState("");
588
+ const [tagDraft, setTagDraft] = React2.useState("");
589
+ const [relationTarget, setRelationTarget] = React2.useState("");
590
+ const [relationType, setRelationType] = React2.useState("relates_to");
542
591
  React2.useEffect(() => {
543
592
  persistTheme(theme);
544
593
  }, [theme]);
@@ -560,6 +609,35 @@ function MemoryPage() {
560
609
  return response.json();
561
610
  }).then((data2) => setState({ data: data2 })).catch((error) => setState({ error: String(error) }));
562
611
  };
612
+ const refreshGraph = () => fetch("/api/agent-memory/graph", { headers: { accept: "application/json" } }).then((response) => {
613
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
614
+ return response.json();
615
+ }).then((data2) => setTree({ data: data2 })).catch((error) => setTree({ error: String(error) }));
616
+ const openTree = () => {
617
+ setPanel("tree");
618
+ setTree({ loading: true });
619
+ setTreeProject(null);
620
+ setFocusId(null);
621
+ setDetail(null);
622
+ refreshGraph();
623
+ };
624
+ React2.useEffect(() => {
625
+ if (detail && detail.data) {
626
+ setTagDraft((detail.data.tags || []).join(", "));
627
+ setRelationTarget("");
628
+ }
629
+ }, [detail]);
630
+ React2.useEffect(() => {
631
+ if (panel !== "tree") {
632
+ setTreeProject(null);
633
+ setFocusId(null);
634
+ setDetail(null);
635
+ setNewProjectOpen(false);
636
+ setNewProjectName("");
637
+ setSelectedIds([]);
638
+ setMoveTarget("");
639
+ }
640
+ }, [panel]);
563
641
  React2.useEffect(load, []);
564
642
  const openItem = (id) => {
565
643
  setDetail({ loading: true });
@@ -755,6 +833,404 @@ ${lines.join("\n")}`);
755
833
  load();
756
834
  refreshProposals();
757
835
  };
836
+ const graphData = tree && tree.data ? tree.data : null;
837
+ const graphNodes = graphData ? graphData.nodes : [];
838
+ const graphEdges = graphData ? graphData.edges : [];
839
+ const focusNode = focusId === null ? null : graphNodes.find((node) => node.id === focusId) || null;
840
+ const explicitNeighbors = focusNode ? graphEdges.filter((edge) => edge.from === focusNode.id || edge.to === focusNode.id).map((edge) => {
841
+ const id = edge.from === focusNode.id ? edge.to : edge.from;
842
+ return { node: graphNodes.find((node) => node.id === id), edge };
843
+ }).filter((entry) => entry.node) : [];
844
+ const explicitIds = new Set(explicitNeighbors.map((entry) => entry.node.id));
845
+ const suggestedNeighbors = focusNode ? graphNodes.filter((node) => {
846
+ if (node.id === focusNode.id || explicitIds.has(node.id)) return false;
847
+ if (node.project !== focusNode.project) return false;
848
+ return (node.tags || []).some((tag) => (focusNode.tags || []).includes(tag));
849
+ }).map((node) => ({ node, edge: { type: "same_topic", from: focusNode.id, to: node.id } })) : [];
850
+ const focusNeighbors = [...explicitNeighbors, ...suggestedNeighbors].slice(0, 8);
851
+ const renderTreeCards = () => {
852
+ if (!graphData) return null;
853
+ return React2.createElement(
854
+ "div",
855
+ { style: { display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(220px, 1fr))", gap: "12px", marginTop: "6px" } },
856
+ graphData.projects.map(
857
+ (project) => React2.createElement(
858
+ "button",
859
+ {
860
+ key: project.name,
861
+ className: "am-row",
862
+ style: { display: "block", width: "100%", textAlign: "left", padding: "14px", cursor: "pointer" },
863
+ onClick: () => {
864
+ setTreeProject(project.name);
865
+ setFocusId(null);
866
+ setDetail(null);
867
+ setSelectedIds([]);
868
+ setMoveTarget("");
869
+ }
870
+ },
871
+ React2.createElement("div", { style: { fontWeight: 600, marginBottom: "4px" } }, project.name),
872
+ React2.createElement("div", { style: styles.small }, `${project.count} 条记忆`),
873
+ graphData.conflicts && graphData.conflicts.some((conflict) => conflict.project === project.name) ? React2.createElement("div", { style: { ...styles.small, color: "var(--am-warn)" } }, `相似提示 ${graphData.conflicts.filter((conflict) => conflict.project === project.name).length}`) : null,
874
+ React2.createElement(
875
+ "div",
876
+ { style: { ...styles.small, color: "var(--am-text-tertiary)", marginTop: "2px" } },
877
+ project.last_updated ? `最近更新 ${project.last_updated}` : "还没有记忆"
878
+ ),
879
+ project.name === "未归类" ? null : React2.createElement(
880
+ "span",
881
+ {
882
+ style: { ...styles.small, color: "var(--am-danger)", marginTop: "6px", display: "inline-block" },
883
+ onClick: (event) => {
884
+ event.stopPropagation();
885
+ if (!window.confirm(`删除项目「${project.name}」?里面的记忆会回到「未归类」,不会被删除。`)) return;
886
+ fetch(`/api/agent-memory/projects?name=${encodeURIComponent(project.name)}`, { method: "DELETE" }).then(async (response) => {
887
+ const data2 = await response.json().catch(() => ({}));
888
+ if (!response.ok) throw new Error(data2.error || `HTTP ${response.status}`);
889
+ openTree();
890
+ }).catch((error) => setNotice(`删除项目失败:${String(error.message || error)}`));
891
+ }
892
+ },
893
+ "删除项目"
894
+ )
895
+ )
896
+ )
897
+ );
898
+ };
899
+ const renderFocus = () => {
900
+ if (focusNode === null) {
901
+ return React2.createElement(
902
+ "div",
903
+ { style: { ...styles.small, padding: "18px" } },
904
+ "点左侧一条记忆,查看它的局部关系。"
905
+ );
906
+ }
907
+ if (focusNeighbors.length === 0) {
908
+ return React2.createElement(
909
+ "div",
910
+ { style: { display: "grid", gap: "10px" } },
911
+ React2.createElement("h3", { style: { margin: 0, fontSize: "15px" } }, `${focusNode.serial || "—"} ${focusNode.display}`),
912
+ React2.createElement(
913
+ "div",
914
+ { style: { ...styles.small, padding: "16px", background: "var(--am-surface-muted)", borderRadius: "12px" } },
915
+ "暂无显式关系;同项目、同标签的条目也没有。给这条记忆加标签或关系后,这里会出现局部图。"
916
+ )
917
+ );
918
+ }
919
+ const width = 460;
920
+ const height = 280;
921
+ const centerX = width / 2;
922
+ const centerY = height / 2;
923
+ const radius = 92;
924
+ const around = focusNeighbors.slice(0, 8).map((entry, index) => {
925
+ const angle = Math.PI * 2 * index / Math.max(1, Math.min(8, focusNeighbors.length));
926
+ return { ...entry, x: centerX + Math.cos(angle) * radius, y: centerY + Math.sin(angle) * radius };
927
+ });
928
+ const label = (node) => (node.display ? node.display.replace(/^[FPE]-\d+\s*/, "") : node.id).slice(0, 16);
929
+ return React2.createElement(
930
+ "div",
931
+ { style: { display: "grid", gridTemplateColumns: "minmax(0, 1fr)", gap: "12px" } },
932
+ React2.createElement(
933
+ "svg",
934
+ { viewBox: `0 0 ${width} ${height}`, style: { width: "100%", height: "280px", background: "var(--am-surface-muted)", borderRadius: "12px" } },
935
+ ...around.map(
936
+ (entry) => React2.createElement("line", {
937
+ key: `line-${entry.node.id}`,
938
+ x1: centerX,
939
+ y1: centerY,
940
+ x2: entry.x,
941
+ y2: entry.y,
942
+ stroke: entry.edge.type === "same_topic" ? "var(--am-border)" : "var(--am-border-strong)",
943
+ strokeWidth: entry.edge.type === "same_topic" ? 1 : 1.2,
944
+ strokeDasharray: entry.edge.type === "same_topic" ? "4 3" : void 0
945
+ })
946
+ ),
947
+ React2.createElement("circle", { cx: centerX, cy: centerY, r: 34, fill: "var(--am-accent-soft)", stroke: "var(--am-accent)" }),
948
+ React2.createElement(
949
+ "text",
950
+ { x: centerX, y: centerY + 4, textAnchor: "middle", fontSize: 10, fill: "var(--am-text)" },
951
+ focusNode.serial || "SEL"
952
+ ),
953
+ ...around.flatMap((entry) => [
954
+ React2.createElement("circle", {
955
+ key: `node-${entry.node.id}`,
956
+ cx: entry.x,
957
+ cy: entry.y,
958
+ r: 20,
959
+ fill: "var(--am-surface)",
960
+ stroke: `var(--am-${entry.node.kind})`,
961
+ strokeWidth: 2,
962
+ style: { cursor: "pointer" },
963
+ onClick: () => {
964
+ setFocusId(entry.node.id);
965
+ openItem(entry.node.id);
966
+ }
967
+ }),
968
+ React2.createElement(
969
+ "text",
970
+ { key: `text-${entry.node.id}`, x: entry.x, y: entry.y + 4, textAnchor: "middle", fontSize: 8, fill: "var(--am-text-secondary)" },
971
+ label(entry.node)
972
+ )
973
+ ])
974
+ )
975
+ );
976
+ };
977
+ const renderTreePanel = () => {
978
+ if (!tree || tree.loading) return React2.createElement("div", { style: styles.small }, "正在加载项目…");
979
+ if (tree.error) return React2.createElement("div", { style: styles.small }, `加载失败:${tree.error}`);
980
+ if (treeProject === null) {
981
+ return React2.createElement(
982
+ "div",
983
+ null,
984
+ React2.createElement("h2", { style: styles.groupTitle }, "项目"),
985
+ React2.createElement("p", { style: styles.small }, "按项目查看记忆;点一个项目进入工作树。"),
986
+ React2.createElement(
987
+ "div",
988
+ { style: { display: "flex", gap: "8px", alignItems: "center", margin: "8px 0 12px", flexWrap: "wrap" } },
989
+ newProjectOpen ? React2.createElement(
990
+ React2.Fragment,
991
+ null,
992
+ React2.createElement("input", {
993
+ className: "am-input",
994
+ style: { width: "240px" },
995
+ placeholder: "新项目名称(例如 Agent Memory)",
996
+ autoFocus: true,
997
+ value: newProjectName,
998
+ onChange: (event) => setNewProjectName(event.target.value),
999
+ onKeyDown: (event) => {
1000
+ if (event.key === "Enter") {
1001
+ const name = newProjectName.trim();
1002
+ if (name === "") return;
1003
+ postJson("/api/agent-memory/projects", { name }).then(() => {
1004
+ setNewProjectOpen(false);
1005
+ setNewProjectName("");
1006
+ openTree();
1007
+ }).catch((error) => setNotice(`新建项目失败:${String(error.message || error)}`));
1008
+ }
1009
+ }
1010
+ }),
1011
+ React2.createElement(AmButton, {
1012
+ variant: "primary",
1013
+ onClick: () => {
1014
+ const name = newProjectName.trim();
1015
+ if (name === "") return;
1016
+ postJson("/api/agent-memory/projects", { name }).then(() => {
1017
+ setNewProjectOpen(false);
1018
+ setNewProjectName("");
1019
+ openTree();
1020
+ }).catch((error) => setNotice(`新建项目失败:${String(error.message || error)}`));
1021
+ }
1022
+ }, "创建"),
1023
+ React2.createElement(AmButton, { variant: "ghost", onClick: () => {
1024
+ setNewProjectOpen(false);
1025
+ setNewProjectName("");
1026
+ } }, "取消")
1027
+ ) : React2.createElement(AmButton, { variant: "ghost", onClick: () => setNewProjectOpen(true) }, "+ 新建项目")
1028
+ ),
1029
+ renderTreeCards()
1030
+ );
1031
+ }
1032
+ const docs = graphNodes.filter((node) => node.project === treeProject);
1033
+ const detailCard = focusNode && detail && detail.data ? (() => {
1034
+ const current = detail.data;
1035
+ const relations = current.relations || [];
1036
+ const candidates = graphNodes.filter((node) => node.id !== current.id && node.project === treeProject);
1037
+ const saveTags = () => {
1038
+ const tags = tagDraft.split(",").map((tag) => tag.trim()).filter((tag) => tag !== "");
1039
+ postJson("/api/agent-memory/meta", { id: current.id, tags }).then(() => {
1040
+ refreshGraph();
1041
+ openItem(current.id);
1042
+ }).catch((error) => setNotice(`保存标签失败:${String(error.message || error)}`));
1043
+ };
1044
+ const saveRelations = (next) => {
1045
+ postJson("/api/agent-memory/meta", { id: current.id, relations: next }).then(() => {
1046
+ refreshGraph();
1047
+ openItem(current.id);
1048
+ }).catch((error) => setNotice(`保存关系失败:${String(error.message || error)}`));
1049
+ };
1050
+ return React2.createElement(
1051
+ "div",
1052
+ { style: { border: "1px solid var(--am-border)", background: "var(--am-surface)", borderRadius: "12px", padding: "18px", marginBottom: "14px" } },
1053
+ React2.createElement("h2", { style: { margin: 0, fontSize: "18px" } }, current.abstract),
1054
+ React2.createElement(
1055
+ "div",
1056
+ { style: { display: "grid", gap: "10px", borderTop: "1px solid var(--am-border)", paddingTop: "12px" } },
1057
+ React2.createElement(
1058
+ "div",
1059
+ { style: { display: "flex", gap: "8px", alignItems: "center", flexWrap: "wrap" } },
1060
+ React2.createElement("span", { style: { ...styles.small, fontWeight: 600 } }, "标签"),
1061
+ React2.createElement("input", {
1062
+ className: "am-input",
1063
+ style: { width: "260px" },
1064
+ placeholder: "逗号分隔,例如 v4, ui, release",
1065
+ value: tagDraft,
1066
+ onChange: (event) => setTagDraft(event.target.value),
1067
+ onKeyDown: (event) => {
1068
+ if (event.key === "Enter") saveTags();
1069
+ }
1070
+ }),
1071
+ React2.createElement(AmButton, { variant: "ghost", onClick: saveTags }, "保存标签"),
1072
+ React2.createElement("span", { style: styles.small }, "最多 8 个,每个 ≤32 字符")
1073
+ ),
1074
+ React2.createElement(
1075
+ "div",
1076
+ { style: { display: "flex", gap: "8px", alignItems: "center", flexWrap: "wrap" } },
1077
+ React2.createElement("span", { style: { ...styles.small, fontWeight: 600 } }, "关系"),
1078
+ ...relations.map(
1079
+ (relation) => React2.createElement(
1080
+ "span",
1081
+ { key: `${relation.type}:${relation.target}`, style: { ...styles.small, background: "var(--am-surface-muted)", padding: "2px 7px", borderRadius: "999px" } },
1082
+ `${relation.type === "relates_to" ? "相关" : "来源"} → ${(graphNodes.find((node) => node.id === relation.target) || { display: relation.target }).display}`,
1083
+ " ",
1084
+ React2.createElement("span", {
1085
+ style: { cursor: "pointer", color: "var(--am-danger)" },
1086
+ onClick: () => saveRelations(relations.filter((entry) => entry.target !== relation.target || entry.type !== relation.type))
1087
+ }, "×")
1088
+ )
1089
+ ),
1090
+ relations.length === 0 ? React2.createElement("span", { style: styles.small }, "暂无关系,可在下方添加") : null
1091
+ ),
1092
+ React2.createElement(
1093
+ "div",
1094
+ { style: { display: "flex", gap: "8px", alignItems: "center", flexWrap: "wrap" } },
1095
+ React2.createElement(
1096
+ "select",
1097
+ {
1098
+ className: "am-input",
1099
+ style: { width: "120px" },
1100
+ value: relationType,
1101
+ onChange: (event) => setRelationType(event.target.value)
1102
+ },
1103
+ React2.createElement("option", { value: "relates_to" }, "相关"),
1104
+ React2.createElement("option", { value: "derived_from" }, "来源")
1105
+ ),
1106
+ React2.createElement(
1107
+ "select",
1108
+ {
1109
+ className: "am-input",
1110
+ style: { width: "240px" },
1111
+ value: relationTarget,
1112
+ onChange: (event) => setRelationTarget(event.target.value)
1113
+ },
1114
+ React2.createElement("option", { value: "" }, "选择目标记忆…"),
1115
+ ...candidates.map((node) => React2.createElement("option", { key: node.id, value: node.id }, node.display))
1116
+ ),
1117
+ React2.createElement(AmButton, {
1118
+ variant: "ghost",
1119
+ disabled: relationTarget === "",
1120
+ onClick: () => saveRelations([...relations, { type: relationType, target: relationTarget }])
1121
+ }, "添加关系"),
1122
+ React2.createElement("span", { style: styles.small }, "关系只连接同项目记忆;用于工作树,不改变记忆内容")
1123
+ )
1124
+ )
1125
+ );
1126
+ })() : null;
1127
+ return React2.createElement(
1128
+ "div",
1129
+ null,
1130
+ detailCard,
1131
+ React2.createElement(
1132
+ "div",
1133
+ { style: { display: "flex", alignItems: "center", gap: "10px", marginBottom: "12px", flexWrap: "wrap" } },
1134
+ React2.createElement(AmButton, { variant: "ghost", onClick: () => {
1135
+ setTreeProject(null);
1136
+ setFocusId(null);
1137
+ setDetail(null);
1138
+ setSelectedIds([]);
1139
+ } }, "← 全部项目"),
1140
+ React2.createElement("h2", { style: { ...styles.groupTitle, margin: 0 } }, treeProject),
1141
+ React2.createElement("span", { style: styles.small }, `${docs.length} 条`),
1142
+ React2.createElement(
1143
+ "span",
1144
+ { style: { marginLeft: "auto", display: "flex", alignItems: "center", gap: "8px" } },
1145
+ React2.createElement(
1146
+ "label",
1147
+ { style: styles.small },
1148
+ React2.createElement("input", {
1149
+ type: "checkbox",
1150
+ checked: docs.length > 0 && selectedIds.length === docs.length,
1151
+ onChange: (event) => setSelectedIds(event.target.checked ? docs.map((node) => node.id) : [])
1152
+ }),
1153
+ " 全选"
1154
+ ),
1155
+ React2.createElement("span", { style: styles.small }, `已选 ${selectedIds.length}`),
1156
+ React2.createElement(
1157
+ "select",
1158
+ {
1159
+ className: "am-input",
1160
+ style: { width: "150px" },
1161
+ value: moveTarget,
1162
+ onChange: (event) => setMoveTarget(event.target.value)
1163
+ },
1164
+ React2.createElement("option", { value: "" }, "移动到…"),
1165
+ React2.createElement("option", { value: "__none__" }, "未归类"),
1166
+ ...graphData.projects.filter((project) => project.name !== treeProject).map(
1167
+ (project) => React2.createElement("option", { key: project.name, value: project.name }, project.name)
1168
+ )
1169
+ ),
1170
+ React2.createElement(AmButton, {
1171
+ variant: "primary",
1172
+ disabled: selectedIds.length === 0 || moveTarget === "",
1173
+ onClick: () => {
1174
+ const target = moveTarget === "__none__" ? null : moveTarget;
1175
+ postJson("/api/agent-memory/projects/move", { ids: selectedIds, project: target }).then(() => {
1176
+ setSelectedIds([]);
1177
+ setMoveTarget("");
1178
+ openTree();
1179
+ }).catch((error) => setNotice(`移动失败:${String(error.message || error)}`));
1180
+ }
1181
+ }, "移动")
1182
+ )
1183
+ ),
1184
+ React2.createElement(
1185
+ "div",
1186
+ { style: { display: "grid", gridTemplateColumns: "minmax(220px, 320px) minmax(0, 1fr)", gap: "18px", alignItems: "start" } },
1187
+ React2.createElement(
1188
+ "div",
1189
+ { className: "am-tree-panel" },
1190
+ ...["fact", "pitfall", "exp"].map((kind) => {
1191
+ const rows = docs.filter((node) => node.kind === kind);
1192
+ if (rows.length === 0) return null;
1193
+ return React2.createElement(
1194
+ "div",
1195
+ { key: kind, className: "am-tree-group" },
1196
+ React2.createElement(
1197
+ "div",
1198
+ { className: "am-tree-group-title" },
1199
+ React2.createElement("span", null, KIND_STYLE[kind] ? KIND_STYLE[kind].label : kind),
1200
+ React2.createElement("span", null, String(rows.length))
1201
+ ),
1202
+ ...rows.map(
1203
+ (node) => React2.createElement(
1204
+ "div",
1205
+ { key: node.id, className: `am-tree-row${focusId === node.id ? " is-selected" : ""}` },
1206
+ React2.createElement("input", {
1207
+ type: "checkbox",
1208
+ checked: selectedIds.includes(node.id),
1209
+ onChange: (event) => setSelectedIds(
1210
+ (current) => event.target.checked ? [...current, node.id] : current.filter((id) => id !== node.id)
1211
+ )
1212
+ }),
1213
+ React2.createElement(
1214
+ "button",
1215
+ {
1216
+ className: "am-tree-link",
1217
+ onClick: () => {
1218
+ setFocusId(node.id);
1219
+ openItem(node.id);
1220
+ }
1221
+ },
1222
+ React2.createElement("div", { className: "am-tree-title" }, node.display),
1223
+ React2.createElement("div", { className: "am-tree-meta" }, `${node.valid_from}${node.tags.length ? ` · ${node.tags.slice(0, 2).join(" ")}` : ""}`)
1224
+ )
1225
+ )
1226
+ )
1227
+ );
1228
+ })
1229
+ ),
1230
+ renderFocus()
1231
+ )
1232
+ );
1233
+ };
758
1234
  const data = state.data;
759
1235
  const total = data ? data.total : "—";
760
1236
  const pending = data ? data.pending : "—";
@@ -807,11 +1283,19 @@ ${lines.join("\n")}`);
807
1283
  React2.createElement(
808
1284
  "p",
809
1285
  { className: "am-subtitle" },
810
- data && data.modules && data.modules.some((module2) => !module2.ok) ? `部分模块不可用:${data.modules.filter((module2) => !module2.ok).map((module2) => module2.name).join("、")}` : "这里是助手记住的事。点一条查看原文与出处;修订会先弹卡片确认。"
1286
+ data && data.modules && data.modules.some((module2) => !module2.ok) ? `部分模块不可用:${data.modules.filter((module2) => !module2.ok).map((module2) => module2.name).join("、")}` : "这里是助手记住的事。点一条查看原文;修订会先弹卡片确认。"
811
1287
  ),
812
1288
  React2.createElement(
813
1289
  "div",
814
1290
  { className: "am-toolbar" },
1291
+ React2.createElement(AmButton, {
1292
+ variant: "ghost",
1293
+ onClick: panel === "tree" ? () => {
1294
+ setPanel(null);
1295
+ setDetail(null);
1296
+ setFocusId(null);
1297
+ } : openTree
1298
+ }, panel === "tree" ? "← 列表" : "项目"),
815
1299
  React2.createElement(
816
1300
  "div",
817
1301
  { className: "am-toolbar-search" },
@@ -852,7 +1336,7 @@ ${lines.join("\n")}`);
852
1336
  )
853
1337
  ),
854
1338
  notice ? React2.createElement("p", { style: styles.notice }, notice) : null,
855
- panel === "create" ? React2.createElement(
1339
+ panel === "tree" ? renderTreePanel() : panel === "create" ? React2.createElement(
856
1340
  "div",
857
1341
  null,
858
1342
  React2.createElement("h2", { style: styles.groupTitle }, "新增一条记忆"),
@@ -1302,21 +1786,21 @@ ${lines.join("\n")}`);
1302
1786
  }
1303
1787
  function MemoryIcon() {
1304
1788
  return React2.createElement(
1305
- "span",
1789
+ "svg",
1306
1790
  {
1307
- style: {
1308
- display: "inline-flex",
1309
- alignItems: "center",
1310
- justifyContent: "center",
1311
- width: "20px",
1312
- height: "20px",
1313
- fontSize: "12px",
1314
- fontWeight: 600,
1315
- borderRadius: "6px",
1316
- border: "1px solid var(--am-border, #e5e7eb)"
1317
- }
1791
+ width: 18,
1792
+ height: 18,
1793
+ viewBox: "0 0 24 24",
1794
+ fill: "none",
1795
+ stroke: "currentColor",
1796
+ strokeWidth: 1.8,
1797
+ strokeLinecap: "round",
1798
+ strokeLinejoin: "round",
1799
+ "aria-hidden": true,
1800
+ style: { display: "block" }
1318
1801
  },
1319
- "记"
1802
+ React2.createElement("path", { d: "M6 4.5h12a1 1 0 0 1 1 1V19.5l-7-3.5-7 3.5V5.5a1 1 0 0 1 1-1Z" }),
1803
+ React2.createElement("path", { d: "M9 8.5h6" })
1320
1804
  );
1321
1805
  }
1322
1806
  function apply(ctx) {