dsh-mnemon 0.1.5 → 0.2.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.
Files changed (230) hide show
  1. package/README.md +125 -104
  2. package/README.zh-CN.md +130 -109
  3. package/THIRD_PARTY_NOTICES.md +7 -0
  4. package/lib/client.js +4193 -1128
  5. package/lib/index.js +4326 -307
  6. package/lib/types/activity.d.ts +2 -18
  7. package/lib/types/channels.d.ts +1 -4
  8. package/lib/types/client/GlobalLocationSetting.d.ts +19 -0
  9. package/lib/types/client/MnemonLogo.d.ts +0 -1
  10. package/lib/types/client/MnemonPackSection.d.ts +5 -3
  11. package/lib/types/client/MnemonSaveAction.d.ts +1 -2
  12. package/lib/types/client/MnemonSettingsCard.d.ts +5 -4
  13. package/lib/types/client/MnemonTurnTail.d.ts +1 -2
  14. package/lib/types/client/MnemonView.d.ts +1 -3
  15. package/lib/types/client/MnemonViewAppearance.d.ts +0 -1
  16. package/lib/types/client/ProviderIcon.d.ts +8 -0
  17. package/lib/types/client/ProviderSettingsSection.d.ts +16 -0
  18. package/lib/types/client/anchor.d.ts +0 -1
  19. package/lib/types/client/api.d.ts +12 -19
  20. package/lib/types/client/dsh-compat.d.ts +0 -1
  21. package/lib/types/client/index.d.ts +0 -1
  22. package/lib/types/client/locales.d.ts +552 -1
  23. package/lib/types/client/settings.d.ts +4 -3
  24. package/lib/types/client/sidebar-entry.d.ts +0 -1
  25. package/lib/types/client/workspace-controller.d.ts +0 -1
  26. package/lib/types/client/workspace-mount.d.ts +1 -3
  27. package/lib/types/commands.d.ts +0 -1
  28. package/lib/types/config-values.d.ts +0 -1
  29. package/lib/types/config.d.ts +6 -80
  30. package/lib/types/contracts.d.ts +38 -62
  31. package/lib/types/documents.d.ts +2 -79
  32. package/lib/types/guidance.d.ts +0 -1
  33. package/lib/types/index.d.ts +0 -1
  34. package/lib/types/lifecycle.d.ts +32 -47
  35. package/lib/types/live-runtime.d.ts +0 -1
  36. package/lib/types/memory-bodies.d.ts +27 -23
  37. package/lib/types/pack.d.ts +2 -48
  38. package/lib/types/process.d.ts +4 -2
  39. package/lib/types/provider-placement.d.ts +26 -0
  40. package/lib/types/providers/byterover.d.ts +31 -0
  41. package/lib/types/providers/catalog.d.ts +23 -0
  42. package/lib/types/providers/hindsight.d.ts +19 -0
  43. package/lib/types/providers/holographic.d.ts +22 -0
  44. package/lib/types/providers/honcho.d.ts +18 -0
  45. package/lib/types/providers/http.d.ts +32 -0
  46. package/lib/types/providers/mem0.d.ts +17 -0
  47. package/lib/types/providers/openviking.d.ts +31 -0
  48. package/lib/types/providers/provider.d.ts +38 -0
  49. package/lib/types/providers/retaindb.d.ts +16 -0
  50. package/lib/types/providers/supermemory.d.ts +16 -0
  51. package/lib/types/review-activity.d.ts +2 -16
  52. package/lib/types/rpc.d.ts +1 -2
  53. package/lib/types/runner.d.ts +15 -11
  54. package/lib/types/runtime-memory.d.ts +2 -59
  55. package/lib/types/service.d.ts +54 -152
  56. package/lib/types/settings.d.ts +1 -4
  57. package/lib/types/shared/contracts.d.ts +869 -0
  58. package/lib/types/storage-scope.d.ts +2 -29
  59. package/lib/types/subagent.d.ts +11 -16
  60. package/lib/types/tools.d.ts +0 -1
  61. package/lib/types/version-updates.d.ts +2 -29
  62. package/package.json +22 -9
  63. package/docs/README.md +0 -8
  64. package/docs/assets/diagrams/en/memory-system-flow.png +0 -0
  65. package/docs/assets/diagrams/en/project-architecture.svg +0 -182
  66. package/docs/assets/diagrams/zh-CN/memory-system-flow.png +0 -0
  67. package/docs/assets/diagrams/zh-CN/project-architecture.svg +0 -182
  68. package/docs/assets/media/dsh-mnemon-memory-system-demo-poster.jpg +0 -0
  69. package/docs/assets/media/dsh-mnemon-memory-system-demo.gif +0 -0
  70. package/docs/assets/media/dsh-mnemon-memory-system-demo.mp4 +0 -0
  71. package/docs/assets/screenshots/conversation-memory-jump.png +0 -0
  72. package/docs/assets/screenshots/conversation-save-dialog.png +0 -0
  73. package/docs/assets/screenshots/conversation-turn-memory.png +0 -0
  74. package/docs/assets/screenshots/document-create-dialog.png +0 -0
  75. package/docs/assets/screenshots/documents-markdown.png +0 -0
  76. package/docs/assets/screenshots/entities-context.png +0 -0
  77. package/docs/assets/screenshots/memory-content.png +0 -0
  78. package/docs/assets/screenshots/overview-memory-graph.png +0 -0
  79. package/docs/assets/screenshots/recall-agent-answer.png +0 -0
  80. package/docs/assets/screenshots/remember-dialog.png +0 -0
  81. package/docs/assets/screenshots/runtime-memory-add.png +0 -0
  82. package/docs/assets/screenshots/runtime-memory.png +0 -0
  83. package/docs/assets/screenshots/settings-memory-system.png +0 -0
  84. package/docs/assets/screenshots/status-overview.png +0 -0
  85. package/docs/assets/screenshots/version-check.png +0 -0
  86. package/docs/en/README.md +0 -48
  87. package/docs/en/architecture.md +0 -133
  88. package/docs/en/configuration.md +0 -234
  89. package/docs/en/development.md +0 -198
  90. package/docs/en/getting-started.md +0 -199
  91. package/docs/en/interfaces.md +0 -181
  92. package/docs/en/operations.md +0 -173
  93. package/docs/en/project-overview.md +0 -145
  94. package/docs/en/roadmap.md +0 -41
  95. package/docs/en/storage-model.md +0 -185
  96. package/docs/en/ui-guide.md +0 -156
  97. package/docs/en/workflows.md +0 -290
  98. package/docs/zh-CN/README.md +0 -48
  99. package/docs/zh-CN/architecture.md +0 -133
  100. package/docs/zh-CN/configuration.md +0 -234
  101. package/docs/zh-CN/development.md +0 -198
  102. package/docs/zh-CN/getting-started.md +0 -199
  103. package/docs/zh-CN/interfaces.md +0 -181
  104. package/docs/zh-CN/operations.md +0 -173
  105. package/docs/zh-CN/project-overview.md +0 -145
  106. package/docs/zh-CN/roadmap.md +0 -41
  107. package/docs/zh-CN/storage-model.md +0 -185
  108. package/docs/zh-CN/ui-guide.md +0 -160
  109. package/docs/zh-CN/workflows.md +0 -290
  110. package/lib/client.js.map +0 -1
  111. package/lib/types/activity.d.ts.map +0 -1
  112. package/lib/types/activity.js +0 -93
  113. package/lib/types/activity.js.map +0 -1
  114. package/lib/types/channels.d.ts.map +0 -1
  115. package/lib/types/channels.js +0 -4
  116. package/lib/types/channels.js.map +0 -1
  117. package/lib/types/client/MnemonLogo.d.ts.map +0 -1
  118. package/lib/types/client/MnemonLogo.js +0 -6
  119. package/lib/types/client/MnemonLogo.js.map +0 -1
  120. package/lib/types/client/MnemonPackSection.d.ts.map +0 -1
  121. package/lib/types/client/MnemonPackSection.js +0 -136
  122. package/lib/types/client/MnemonPackSection.js.map +0 -1
  123. package/lib/types/client/MnemonSaveAction.d.ts.map +0 -1
  124. package/lib/types/client/MnemonSaveAction.js +0 -90
  125. package/lib/types/client/MnemonSaveAction.js.map +0 -1
  126. package/lib/types/client/MnemonSettingsCard.d.ts.map +0 -1
  127. package/lib/types/client/MnemonSettingsCard.js +0 -155
  128. package/lib/types/client/MnemonSettingsCard.js.map +0 -1
  129. package/lib/types/client/MnemonTurnTail.d.ts.map +0 -1
  130. package/lib/types/client/MnemonTurnTail.js +0 -56
  131. package/lib/types/client/MnemonTurnTail.js.map +0 -1
  132. package/lib/types/client/MnemonView.d.ts.map +0 -1
  133. package/lib/types/client/MnemonView.js +0 -1594
  134. package/lib/types/client/MnemonView.js.map +0 -1
  135. package/lib/types/client/MnemonViewAppearance.d.ts.map +0 -1
  136. package/lib/types/client/MnemonViewAppearance.js +0 -67
  137. package/lib/types/client/MnemonViewAppearance.js.map +0 -1
  138. package/lib/types/client/anchor.d.ts.map +0 -1
  139. package/lib/types/client/anchor.js +0 -46
  140. package/lib/types/client/anchor.js.map +0 -1
  141. package/lib/types/client/api.d.ts.map +0 -1
  142. package/lib/types/client/api.js +0 -150
  143. package/lib/types/client/api.js.map +0 -1
  144. package/lib/types/client/dsh-compat.d.ts.map +0 -1
  145. package/lib/types/client/dsh-compat.js +0 -2
  146. package/lib/types/client/dsh-compat.js.map +0 -1
  147. package/lib/types/client/index.d.ts.map +0 -1
  148. package/lib/types/client/index.js +0 -161
  149. package/lib/types/client/index.js.map +0 -1
  150. package/lib/types/client/locales.d.ts.map +0 -1
  151. package/lib/types/client/locales.js +0 -1172
  152. package/lib/types/client/locales.js.map +0 -1
  153. package/lib/types/client/settings.d.ts.map +0 -1
  154. package/lib/types/client/settings.js +0 -70
  155. package/lib/types/client/settings.js.map +0 -1
  156. package/lib/types/client/sidebar-entry.d.ts.map +0 -1
  157. package/lib/types/client/sidebar-entry.js +0 -134
  158. package/lib/types/client/sidebar-entry.js.map +0 -1
  159. package/lib/types/client/workspace-controller.d.ts.map +0 -1
  160. package/lib/types/client/workspace-controller.js +0 -21
  161. package/lib/types/client/workspace-controller.js.map +0 -1
  162. package/lib/types/client/workspace-mount.d.ts.map +0 -1
  163. package/lib/types/client/workspace-mount.js +0 -138
  164. package/lib/types/client/workspace-mount.js.map +0 -1
  165. package/lib/types/commands.d.ts.map +0 -1
  166. package/lib/types/commands.js +0 -104
  167. package/lib/types/commands.js.map +0 -1
  168. package/lib/types/config-values.d.ts.map +0 -1
  169. package/lib/types/config-values.js +0 -4
  170. package/lib/types/config-values.js.map +0 -1
  171. package/lib/types/config.d.ts.map +0 -1
  172. package/lib/types/config.js +0 -127
  173. package/lib/types/config.js.map +0 -1
  174. package/lib/types/contracts.d.ts.map +0 -1
  175. package/lib/types/contracts.js +0 -2
  176. package/lib/types/contracts.js.map +0 -1
  177. package/lib/types/documents.d.ts.map +0 -1
  178. package/lib/types/documents.js +0 -495
  179. package/lib/types/documents.js.map +0 -1
  180. package/lib/types/guidance.d.ts.map +0 -1
  181. package/lib/types/guidance.js +0 -34
  182. package/lib/types/guidance.js.map +0 -1
  183. package/lib/types/index.d.ts.map +0 -1
  184. package/lib/types/index.js +0 -55
  185. package/lib/types/index.js.map +0 -1
  186. package/lib/types/lifecycle.d.ts.map +0 -1
  187. package/lib/types/lifecycle.js +0 -456
  188. package/lib/types/lifecycle.js.map +0 -1
  189. package/lib/types/live-runtime.d.ts.map +0 -1
  190. package/lib/types/live-runtime.js +0 -146
  191. package/lib/types/live-runtime.js.map +0 -1
  192. package/lib/types/memory-bodies.d.ts.map +0 -1
  193. package/lib/types/memory-bodies.js +0 -186
  194. package/lib/types/memory-bodies.js.map +0 -1
  195. package/lib/types/pack.d.ts.map +0 -1
  196. package/lib/types/pack.js +0 -709
  197. package/lib/types/pack.js.map +0 -1
  198. package/lib/types/process.d.ts.map +0 -1
  199. package/lib/types/process.js +0 -65
  200. package/lib/types/process.js.map +0 -1
  201. package/lib/types/review-activity.d.ts.map +0 -1
  202. package/lib/types/review-activity.js +0 -37
  203. package/lib/types/review-activity.js.map +0 -1
  204. package/lib/types/rpc.d.ts.map +0 -1
  205. package/lib/types/rpc.js +0 -384
  206. package/lib/types/rpc.js.map +0 -1
  207. package/lib/types/runner.d.ts.map +0 -1
  208. package/lib/types/runner.js +0 -198
  209. package/lib/types/runner.js.map +0 -1
  210. package/lib/types/runtime-memory.d.ts.map +0 -1
  211. package/lib/types/runtime-memory.js +0 -415
  212. package/lib/types/runtime-memory.js.map +0 -1
  213. package/lib/types/service.d.ts.map +0 -1
  214. package/lib/types/service.js +0 -512
  215. package/lib/types/service.js.map +0 -1
  216. package/lib/types/settings.d.ts.map +0 -1
  217. package/lib/types/settings.js +0 -103
  218. package/lib/types/settings.js.map +0 -1
  219. package/lib/types/storage-scope.d.ts.map +0 -1
  220. package/lib/types/storage-scope.js +0 -179
  221. package/lib/types/storage-scope.js.map +0 -1
  222. package/lib/types/subagent.d.ts.map +0 -1
  223. package/lib/types/subagent.js +0 -624
  224. package/lib/types/subagent.js.map +0 -1
  225. package/lib/types/tools.d.ts.map +0 -1
  226. package/lib/types/tools.js +0 -341
  227. package/lib/types/tools.js.map +0 -1
  228. package/lib/types/version-updates.d.ts.map +0 -1
  229. package/lib/types/version-updates.js +0 -383
  230. package/lib/types/version-updates.js.map +0 -1
@@ -1,1594 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { createContext, Fragment, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
3
- import { IconChevronLeftOutline14 } from '@deepseek-ai/dsh-client-ui-primitives';
4
- import { consumeMnemonAnchor, subscribeMnemonAnchor } from "./anchor.js";
5
- import Markdown from 'markdown-to-jsx';
6
- import { CATEGORIES, } from "../service.js";
7
- import { MnemonClient } from "./api.js";
8
- import { translateZh } from "./locales.js";
9
- import { MnemonLogo } from "./MnemonLogo.js";
10
- import { appearanceClass, MnemonViewAppearanceProvider, resolveMnemonViewAppearance, useMnemonViewAppearance, } from "./MnemonViewAppearance.js";
11
- import css from './MnemonView.module.css';
12
- /** 系统 → 三层存储 → 读写工具;组间以分隔线呈现。 */
13
- const PAGE_NAV = [
14
- {
15
- aria: 'nav.group.system',
16
- entries: [
17
- { id: 'status', label: 'nav.status', detail: 'nav.status.detail', glyph: '⌘' },
18
- ],
19
- },
20
- {
21
- aria: 'nav.group.storage',
22
- entries: [
23
- { id: 'runtime', label: 'nav.runtime', detail: 'nav.runtime.detail', glyph: '◫' },
24
- { id: 'overview', label: 'nav.bodies', detail: 'nav.bodies.detail', glyph: '◇' },
25
- { id: 'documents', label: 'nav.documents', detail: 'nav.documents.detail', glyph: '▤' },
26
- ],
27
- },
28
- {
29
- aria: 'nav.group.tools',
30
- entries: [
31
- { id: 'remember', label: 'nav.remember', detail: 'nav.remember.detail', glyph: '+' },
32
- { id: 'explore', label: 'nav.search', detail: 'nav.search.detail', glyph: '⌕' },
33
- { id: 'entities', label: 'nav.entities', detail: 'nav.entities.detail', glyph: '◎' },
34
- { id: 'list', label: 'nav.content', detail: 'nav.content.detail', glyph: '≡' },
35
- ],
36
- },
37
- ];
38
- const SIDEBAR_PAGE_TABS = [
39
- { id: 'status', label: 'nav.status', detail: 'nav.status.detail', glyph: '⌘' },
40
- { id: 'runtime', label: 'nav.runtime', detail: 'nav.runtime.detail', glyph: '◫' },
41
- { id: 'overview', label: 'nav.bodies', detail: 'nav.bodies.detail', glyph: '◇' },
42
- { id: 'documents', label: 'nav.documents', detail: 'nav.documents.detail', glyph: '▤' },
43
- ];
44
- const MEMORY_PAGE_TABS = [
45
- { id: 'overview', label: 'nav.overview' },
46
- { id: 'explore', label: 'nav.search' },
47
- { id: 'list', label: 'nav.content' },
48
- { id: 'entities', label: 'nav.entities' },
49
- ];
50
- const MEMORY_PAGES = new Set(MEMORY_PAGE_TABS.map(item => item.id));
51
- function isMemoryPage(page) {
52
- return MEMORY_PAGES.has(page);
53
- }
54
- const CATEGORY_KEYS = {
55
- decision: 'category.decision',
56
- preference: 'category.preference',
57
- fact: 'category.fact',
58
- insight: 'category.insight',
59
- context: 'category.context',
60
- general: 'category.general',
61
- };
62
- const I18nContext = createContext(translateZh);
63
- const LocaleContext = createContext('zh');
64
- function useT() { return useContext(I18nContext); }
65
- function useLocale() { return useContext(LocaleContext) === 'en' ? 'en-US' : 'zh-CN'; }
66
- function categoryLabel(t, category) { return CATEGORY_KEYS[category] === undefined ? category : t(CATEGORY_KEYS[category]); }
67
- function humanBytes(bytes) {
68
- if (bytes < 1024)
69
- return `${bytes} B`;
70
- if (bytes < 1024 * 1024)
71
- return `${(bytes / 1024).toFixed(1)} KB`;
72
- return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
73
- }
74
- function message(error) {
75
- return error instanceof Error ? error.message : String(error);
76
- }
77
- function short(value, max) {
78
- return value.length <= max ? value : `${value.slice(0, max - 1)}…`;
79
- }
80
- function insightKey(insight) {
81
- return `${insight.memoryBodyId ?? 'memory'}:${insight.id}`;
82
- }
83
- function PageHeader(props) {
84
- const appearance = useMnemonViewAppearance();
85
- return (_jsxs("div", { className: appearanceClass(css.pageHeader, appearance.classes.pageHeader), children: [_jsxs("div", { children: [_jsx("h2", { children: props.title }), _jsx("p", { children: props.description })] }), _jsxs("div", { className: css.pageHeaderMeta, children: [props.meta !== undefined && _jsx("code", { children: props.meta }), props.action] })] }));
86
- }
87
- function ProgressiveFooter(props) {
88
- const t = useT();
89
- if (props.total === 0)
90
- return null;
91
- const remaining = Math.max(0, props.total - props.visible);
92
- return _jsxs("div", { className: props.compact === true ? css.compactListProgress : css.listProgress, children: [_jsx("span", { children: t('common.showing', { visible: props.visible, total: props.total }) }), remaining > 0 && _jsx("button", { type: "button", className: css.secondaryButton, onClick: props.onMore, children: t('common.showMore', { count: Math.min(props.pageSize, remaining) }) })] });
93
- }
94
- /** DSH-style action dialog shared by Sidebar add/write flows. */
95
- function SidebarModal(props) {
96
- const t = useT();
97
- const appearance = useMnemonViewAppearance();
98
- const dialogRef = useRef(null);
99
- const returnFocusRef = useRef(null);
100
- const close = useCallback(() => { if (props.busy !== true)
101
- props.onClose(); }, [props.busy, props.onClose]);
102
- useLayoutEffect(() => {
103
- returnFocusRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
104
- const firstControl = dialogRef.current?.querySelector('[data-autofocus]')
105
- ?? dialogRef.current?.querySelector('input:not(:disabled), textarea:not(:disabled), select:not(:disabled)')
106
- ?? dialogRef.current?.querySelector('div:last-child button:not(:disabled)');
107
- firstControl?.focus({ preventScroll: true });
108
- return () => { if (returnFocusRef.current?.isConnected === true)
109
- returnFocusRef.current.focus({ preventScroll: true }); };
110
- }, []);
111
- useEffect(() => {
112
- const onKey = (event) => {
113
- if (event.key === 'Escape') {
114
- event.preventDefault();
115
- close();
116
- return;
117
- }
118
- if (event.key !== 'Tab')
119
- return;
120
- const controls = Array.from(dialogRef.current?.querySelectorAll('button:not(:disabled), input:not(:disabled), textarea:not(:disabled), select:not(:disabled), a[href], [tabindex]:not([tabindex="-1"])') ?? []).filter(control => control.getAttribute('aria-hidden') !== 'true');
121
- const first = controls[0];
122
- const last = controls.at(-1);
123
- if (first === undefined || last === undefined) {
124
- event.preventDefault();
125
- return;
126
- }
127
- const active = document.activeElement;
128
- if (event.shiftKey && (active === first || !dialogRef.current?.contains(active))) {
129
- event.preventDefault();
130
- last.focus();
131
- }
132
- else if (!event.shiftKey && (active === last || !dialogRef.current?.contains(active))) {
133
- event.preventDefault();
134
- first.focus();
135
- }
136
- };
137
- window.addEventListener('keydown', onKey);
138
- return () => window.removeEventListener('keydown', onKey);
139
- }, [close]);
140
- return (_jsx("div", { className: appearanceClass(css.modalBackdrop, appearance.classes.modalBackdrop), onPointerDown: event => { if (event.target === event.currentTarget)
141
- close(); }, children: _jsxs("section", { ref: dialogRef, className: appearanceClass(css.modal, appearance.classes.modal), role: "dialog", "aria-modal": "true", "aria-label": props.title, children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("h2", { children: props.title }), props.description !== undefined && _jsx("p", { children: props.description })] }), _jsx("button", { type: "button", className: css.iconButton, disabled: props.busy, onClick: close, "aria-label": t('common.cancel'), children: "\u00D7" })] }), _jsx("div", { children: props.children })] }) }));
142
- }
143
- function EmptyState(props) {
144
- return (_jsxs("div", { className: css.emptyState, children: [_jsx("div", { className: css.emptyGlyph, "aria-hidden": "true", children: _jsx("span", { children: props.glyph }) }), _jsxs("div", { children: [_jsx("h3", { children: props.title }), _jsx("p", { children: props.children })] })] }));
145
- }
146
- /** Sidebar mirrors the SSH panel's flat tab model; Buildin keeps the grouped navigation unchanged. */
147
- function WorkspaceNavigation(props) {
148
- const t = useT();
149
- const appearance = useMnemonViewAppearance();
150
- return (_jsxs("div", { className: appearanceClass(css.topNavigation, appearance.classes.topNavigation), children: [appearance.surface === 'sidebar'
151
- ? _jsx("div", { className: appearanceClass(css.nav, appearance.classes.nav), role: "tablist", "aria-label": t('nav.aria'), children: SIDEBAR_PAGE_TABS.map(item => {
152
- const active = item.id === 'overview' ? isMemoryPage(props.page) : props.page === item.id;
153
- return _jsx("button", { type: "button", role: "tab", "aria-selected": active, "data-active": active ? '' : undefined, onClick: () => props.onSelect(item.id), children: t(item.label) }, item.id);
154
- }) })
155
- : _jsx("nav", { className: appearanceClass(css.nav, appearance.classes.nav), "aria-label": t('nav.aria'), children: PAGE_NAV.map((group, groupIndex) => _jsxs(Fragment, { children: [_jsx("div", { className: appearanceClass(css.navGroup, appearance.classes.navGroup), role: "group", "aria-label": t(group.aria), children: group.entries.map(item => _jsxs("button", { type: "button", "aria-current": props.page === item.id ? 'page' : undefined, onClick: () => props.onSelect(item.id), children: [appearance.showNavigationGlyphs && _jsx("span", { className: css.navGlyph, "aria-hidden": "true", children: item.glyph }), _jsxs("span", { children: [_jsx("strong", { children: t(item.label) }), appearance.showNavigationDetails && _jsx("small", { children: t(item.detail) })] })] }, item.id)) }), appearance.showNavigationDividers && groupIndex < PAGE_NAV.length - 1 && _jsx("span", { className: css.navGroupDivider, "aria-hidden": "true" })] }, group.aria)) }), appearance.showSpaceSummary && _jsxs("div", { className: css.spaceSummary, children: [_jsx("span", { children: t('sidebar.activeSpaces') }), _jsx("code", { children: props.catalogKnown ? `${props.activeBodies} / ${props.bodyCount}` : '— / —' }), _jsx("small", { children: props.writeEnabled ? t('common.agentSupervised') : t('common.readOnly') })] })] }));
156
- }
157
- /** Memory tools become a focused second-level tab set on the Sidebar surface. */
158
- function MemoryNavigation(props) {
159
- const t = useT();
160
- const appearance = useMnemonViewAppearance();
161
- if (appearance.surface !== 'sidebar' || !isMemoryPage(props.page))
162
- return null;
163
- return (_jsxs("section", { className: appearance.classes.memoryWorkspace, children: [_jsx(PageHeader, { title: t('nav.bodies'), description: t('overview.description'), action: _jsx("button", { type: "button", className: appearanceClass(css.primaryButton, appearance.classes.memoryWriteButton), disabled: !props.writeEnabled, onClick: props.onRemember, children: t('nav.rememberAction') }) }), _jsx("div", { className: appearance.classes.memoryNavigation, children: _jsx("div", { className: appearance.classes.memoryTabs, role: "tablist", "aria-label": t('nav.memory.aria'), children: MEMORY_PAGE_TABS.map(item => {
164
- const active = props.page === item.id;
165
- return _jsx("button", { type: "button", role: "tab", "aria-selected": active, "data-active": active ? '' : undefined, onClick: () => props.onSelect(item.id), children: t(item.label) }, item.id);
166
- }) }) })] }));
167
- }
168
- /** Full-text popup for a selected graph node whose inspector preview is clamped. */
169
- function ContentPreview(props) {
170
- const t = useT();
171
- const close = useCallback(() => props.onClose(), [props]);
172
- useEffect(() => {
173
- const onKey = (event) => { if (event.key === 'Escape')
174
- props.onClose(); };
175
- window.addEventListener('keydown', onKey);
176
- return () => window.removeEventListener('keydown', onKey);
177
- }, [props]);
178
- const meta = [props.kind, props.node.id, props.node.memoryBodyName].filter((entry) => entry !== undefined).join(' · ');
179
- return (_jsx("div", { className: css.previewOverlay, onPointerDown: event => { if (event.target === event.currentTarget)
180
- props.onClose(); }, children: _jsxs("div", { className: css.previewDialog, role: "dialog", "aria-modal": "true", "aria-label": t('overview.previewTitle'), children: [_jsxs("header", { className: css.previewHeading, children: [_jsx("span", { children: t('overview.previewTitle') }), _jsx("button", { type: "button", onClick: close, "aria-label": t('common.cancel'), children: "\u00D7" })] }), _jsx("div", { className: css.previewMeta, children: meta }), _jsx("div", { className: css.previewBody, children: _jsx("p", { children: props.node.content }) })] }) }));
181
- }
182
- const SAFE_LINK_PATTERN = /^(?:https?:|mailto:|#|\/)/iu;
183
- function safeLink(href) {
184
- if (href == null)
185
- return undefined;
186
- const value = href.trim();
187
- return SAFE_LINK_PATTERN.test(value) ? value : undefined;
188
- }
189
- /** Render managed Markdown without raw HTML and with a deliberately small link surface. */
190
- function DocumentMarkdown(props) {
191
- return (_jsx("div", { className: css.markdownBody, children: _jsx(Markdown, { options: {
192
- disableParsingRawHTML: true,
193
- forceBlock: true,
194
- overrides: {
195
- a: {
196
- component: ({ href, children, ...rest }) => {
197
- const target = safeLink(href);
198
- return target === undefined
199
- ? _jsx("span", { children: children })
200
- : _jsx("a", { ...rest, href: target, target: target.startsWith('http') ? '_blank' : undefined, rel: target.startsWith('http') ? 'noreferrer noopener' : undefined, children: children });
201
- },
202
- },
203
- },
204
- }, children: props.content }) }));
205
- }
206
- function InsightCard(props) {
207
- const t = useT();
208
- const appearance = useMnemonViewAppearance();
209
- const [confirming, setConfirming] = useState(false);
210
- const [forgetting, setForgetting] = useState(false);
211
- const { insight } = props;
212
- const neutralActionClass = appearance.surface === 'sidebar'
213
- ? appearanceClass(css.ghostButton, appearance.classes.itemActionButton)
214
- : css.ghostButton;
215
- const forgetActionClass = appearance.surface === 'sidebar'
216
- ? appearanceClass(css.dangerButton, appearanceClass(appearance.classes.itemActionButton, appearance.classes.itemDangerAction))
217
- : css.dangerButton;
218
- const inlineConfirming = appearance.surface === 'buildin' && confirming;
219
- const meta = [
220
- insight.memoryBodyName,
221
- insight.category !== undefined ? categoryLabel(t, insight.category) : undefined,
222
- insight.importance !== undefined ? t('common.importance', { value: insight.importance }) : undefined,
223
- insight.score !== undefined ? `score ${insight.score.toFixed(3)}` : undefined,
224
- insight.depth !== undefined ? t('common.hops', { count: insight.depth }) : undefined,
225
- ].filter((entry) => entry !== undefined);
226
- const forget = async () => {
227
- setForgetting(true);
228
- try {
229
- await props.onForget(insight);
230
- }
231
- finally {
232
- setForgetting(false);
233
- setConfirming(false);
234
- }
235
- };
236
- return (_jsxs(_Fragment, { children: [_jsxs("article", { className: css.insightCard, children: [_jsxs("div", { className: css.cardTop, children: [_jsx("div", { className: css.badges, children: meta.map(entry => _jsx("span", { className: css.badge, children: entry }, entry)) }), _jsx("code", { className: css.id, title: insight.id, children: insight.id.slice(0, 8) })] }), _jsx("p", { className: css.content, children: insight.content }), (insight.tags?.length ?? 0) > 0 && _jsx("div", { className: css.tags, children: insight.tags.map(tag => _jsxs("span", { children: ["#", tag] }, tag)) }), (insight.entities?.length ?? 0) > 0 && _jsx("div", { className: css.entities, children: insight.entities.map(entity => _jsx("span", { children: entity }, entity)) }), _jsx("div", { className: css.cardActions, children: inlineConfirming ? (_jsxs("div", { className: css.confirmBar, role: "group", "aria-label": t('card.confirmAria'), children: [_jsx("span", { children: t('card.confirmText') }), _jsx("button", { type: "button", className: css.dangerSolidButton, disabled: forgetting, onClick: () => void forget(), children: forgetting ? t('card.processing') : t('card.confirmForget') }), _jsx("button", { type: "button", className: css.ghostButton, disabled: forgetting, onClick: () => setConfirming(false), children: t('common.cancel') })] })) : (_jsxs(_Fragment, { children: [props.onRelated !== undefined && _jsx("button", { type: "button", className: neutralActionClass, onClick: () => props.onRelated?.(insight), children: t('card.related') }), props.onClone !== undefined && _jsx("button", { type: "button", className: neutralActionClass, onClick: () => props.onClone?.(insight), children: t('card.clone') }), _jsx("button", { type: "button", className: neutralActionClass, onClick: () => void navigator.clipboard?.writeText(insight.id), children: t('common.copyId') }), props.writeEnabled && _jsx("button", { type: "button", className: forgetActionClass, onClick: () => setConfirming(true), children: t('card.forget') })] })) })] }), appearance.surface === 'sidebar' && confirming && _jsx(SidebarModal, { title: t('card.confirmText'), description: `${insight.memoryBodyName ?? insight.memoryBodyId ?? ''}${insight.memoryBodyName === undefined && insight.memoryBodyId === undefined ? '' : ' · '}${insight.id}`, busy: forgetting, onClose: () => setConfirming(false), children: _jsxs("div", { className: css.bodyDeleteConfirm, children: [_jsxs("div", { className: css.bodyDeleteSummary, children: [_jsx("p", { className: css.bodyDeleteContent, children: insight.content }), _jsx("span", { children: meta.join(' · ') })] }), _jsxs("div", { className: css.bodyEditActions, children: [_jsx("button", { type: "button", "data-autofocus": true, className: css.ghostButton, disabled: forgetting, onClick: () => setConfirming(false), children: t('common.cancel') }), _jsx("button", { type: "button", className: css.dangerSolidButton, disabled: forgetting, onClick: () => void forget(), children: forgetting ? t('card.processing') : t('card.confirmForget') })] })] }) })] }));
237
- }
238
- const GRAPH_WIDTH = 930;
239
- const GRAPH_HEIGHT = 520;
240
- const GRAPH_MARGIN_X = 58;
241
- const GRAPH_MARGIN_Y = 58;
242
- const CATEGORY_ORDER = ['space', 'entity', 'preference', 'decision', 'fact', 'insight', 'context', 'general'];
243
- function hash(value) {
244
- let result = 2166136261;
245
- for (const char of value)
246
- result = Math.imul(result ^ char.charCodeAt(0), 16777619);
247
- return result >>> 0;
248
- }
249
- function graphNodeKey(node) {
250
- return node.graphId ?? node.id;
251
- }
252
- function graphNodeKind(node) {
253
- return node.kind ?? 'memory';
254
- }
255
- function spaceGraphId(id) {
256
- return `space:${id}`;
257
- }
258
- function entityGraphId(entity) {
259
- return `entity:${encodeURIComponent(normalizeEntity(entity))}`;
260
- }
261
- function normalizeEntity(entity) {
262
- return entity.normalize('NFKC').trim().toLocaleLowerCase();
263
- }
264
- /** Add routing scopes and entity indexes without issuing another recall. */
265
- function enrichMultiSpaceGraph(graph, bodies) {
266
- if (graph.nodes.length === 0)
267
- return graph;
268
- const memories = graph.nodes.map(node => ({ ...node, kind: 'memory' }));
269
- const memoriesByBody = new Map();
270
- for (const node of memories) {
271
- if (node.memoryBodyId === undefined)
272
- continue;
273
- memoriesByBody.set(node.memoryBodyId, [...(memoriesByBody.get(node.memoryBodyId) ?? []), node]);
274
- }
275
- const activeBodies = bodies.filter(body => body.active && ((memoriesByBody.get(body.id)?.length ?? 0) > 0 || (body.stats?.topEntities.length ?? 0) > 0));
276
- const spaceNodes = activeBodies.map(body => ({
277
- id: body.id,
278
- graphId: spaceGraphId(body.id),
279
- kind: 'space',
280
- category: 'space',
281
- content: body.name,
282
- color: '#22a879',
283
- memoryBodyId: body.id,
284
- memoryBodyName: body.name,
285
- occurrenceCount: body.stats?.totalInsights ?? memoriesByBody.get(body.id)?.length ?? 0,
286
- }));
287
- // Native Mnemon entity edges connect two memories. This overview renders
288
- // entities as first-class nodes, so retaining those edges would falsely make
289
- // a memory-to-memory edge look like an entity-to-memory association.
290
- const edges = graph.edges.filter(edge => edge.type !== 'entity');
291
- for (const body of activeBodies) {
292
- for (const memory of memoriesByBody.get(body.id) ?? []) {
293
- edges.push({ sourceId: spaceGraphId(body.id), targetId: graphNodeKey(memory), label: 'scope', color: '#708199', type: 'scope' });
294
- }
295
- }
296
- const bodiesById = new Map(activeBodies.map(body => [body.id, body]));
297
- const indexedEntities = new Map();
298
- for (const memory of memories) {
299
- const body = memory.memoryBodyId === undefined ? undefined : bodiesById.get(memory.memoryBodyId);
300
- if (body === undefined)
301
- continue;
302
- const seen = new Set();
303
- for (const rawEntity of memory.entities ?? []) {
304
- const entity = rawEntity.trim();
305
- const key = normalizeEntity(entity);
306
- if (key === '' || seen.has(key))
307
- continue;
308
- seen.add(key);
309
- const current = indexedEntities.get(key);
310
- if (current === undefined)
311
- indexedEntities.set(key, { entity, memories: [memory], bodies: [body] });
312
- else {
313
- current.memories.push(memory);
314
- if (!current.bodies.some(candidate => candidate.id === body.id))
315
- current.bodies.push(body);
316
- }
317
- }
318
- }
319
- const entities = [...indexedEntities.values()].sort((left, right) => right.memories.length - left.memories.length || left.entity.localeCompare(right.entity)).slice(0, 24);
320
- const entityNodes = entities.map(item => ({
321
- id: item.entity,
322
- graphId: entityGraphId(item.entity),
323
- kind: 'entity',
324
- category: 'entity',
325
- content: item.entity,
326
- color: '#2b9db9',
327
- occurrenceCount: item.memories.length,
328
- memoryBodyIds: item.bodies.map(body => body.id),
329
- memoryBodyNames: item.bodies.map(body => body.name),
330
- }));
331
- for (const item of entities) {
332
- const key = entityGraphId(item.entity);
333
- for (const memory of item.memories)
334
- edges.push({ sourceId: key, targetId: graphNodeKey(memory), label: item.entity, color: '#22a879', type: 'entity' });
335
- }
336
- return { ...graph, nodes: [...spaceNodes, ...entityNodes, ...memories], edges };
337
- }
338
- function graphKindLabel(t, node) {
339
- const kind = graphNodeKind(node);
340
- return kind === 'space' ? t('graph.kindSpace') : kind === 'entity' ? t('graph.kindEntity') : categoryLabel(t, node.category ?? 'general');
341
- }
342
- function activeCategoryAnchors(grouped) {
343
- const categories = [...grouped.keys()].sort((left, right) => {
344
- const leftIndex = CATEGORY_ORDER.indexOf(left);
345
- const rightIndex = CATEGORY_ORDER.indexOf(right);
346
- return (leftIndex < 0 ? CATEGORY_ORDER.length : leftIndex) - (rightIndex < 0 ? CATEGORY_ORDER.length : rightIndex);
347
- });
348
- const anchors = new Map();
349
- if (categories.length === 1) {
350
- anchors.set(categories[0], { x: GRAPH_WIDTH / 2, y: GRAPH_HEIGHT / 2 });
351
- return anchors;
352
- }
353
- categories.forEach((category, index) => {
354
- const angle = -Math.PI / 2 + (index / categories.length) * Math.PI * 2;
355
- anchors.set(category, {
356
- x: GRAPH_WIDTH / 2 + Math.cos(angle) * Math.min(250, 115 + categories.length * 23),
357
- y: GRAPH_HEIGHT / 2 + Math.sin(angle) * Math.min(165, 78 + categories.length * 15),
358
- });
359
- });
360
- return anchors;
361
- }
362
- function clampGraphPosition(position) {
363
- return {
364
- x: Math.min(GRAPH_WIDTH - GRAPH_MARGIN_X, Math.max(GRAPH_MARGIN_X, position.x)),
365
- y: Math.min(GRAPH_HEIGHT - GRAPH_MARGIN_Y, Math.max(GRAPH_MARGIN_Y, position.y)),
366
- };
367
- }
368
- function naturalGraphPositions(nodes, edges) {
369
- const positions = new Map();
370
- const grouped = new Map();
371
- for (const node of nodes) {
372
- const category = node.category ?? 'general';
373
- grouped.set(category, [...(grouped.get(category) ?? []), node]);
374
- }
375
- const anchors = activeCategoryAnchors(grouped);
376
- for (const [category, items] of grouped) {
377
- const anchor = anchors.get(category) ?? { x: GRAPH_WIDTH / 2, y: GRAPH_HEIGHT / 2 };
378
- items.forEach((node, index) => {
379
- const seed = hash(graphNodeKey(node));
380
- const angle = index * 2.399963 + ((seed % 37) / 37) * .4;
381
- const radius = items.length === 1 ? 0 : 24 + Math.sqrt(index + 1) * 35;
382
- positions.set(graphNodeKey(node), clampGraphPosition({ x: anchor.x + Math.cos(angle) * radius, y: anchor.y + Math.sin(angle) * radius }));
383
- });
384
- }
385
- const velocities = new Map(nodes.map(node => [graphNodeKey(node), { x: 0, y: 0 }]));
386
- const visibleIds = new Set(nodes.map(graphNodeKey));
387
- const visibleEdges = edges.filter(edge => visibleIds.has(edge.sourceId) && visibleIds.has(edge.targetId));
388
- for (let iteration = 0; iteration < 150; iteration += 1) {
389
- const cooling = 1 - iteration / 180;
390
- for (let leftIndex = 0; leftIndex < nodes.length; leftIndex += 1) {
391
- const left = nodes[leftIndex];
392
- const leftPosition = positions.get(graphNodeKey(left));
393
- const leftVelocity = velocities.get(graphNodeKey(left));
394
- for (let rightIndex = leftIndex + 1; rightIndex < nodes.length; rightIndex += 1) {
395
- const right = nodes[rightIndex];
396
- const rightPosition = positions.get(graphNodeKey(right));
397
- const rightVelocity = velocities.get(graphNodeKey(right));
398
- let dx = leftPosition.x - rightPosition.x;
399
- let dy = leftPosition.y - rightPosition.y;
400
- if (dx === 0 && dy === 0) {
401
- dx = ((hash(graphNodeKey(left)) % 13) - 6) || 1;
402
- dy = ((hash(graphNodeKey(right)) % 11) - 5) || -1;
403
- }
404
- const distanceSquared = Math.max(100, dx * dx + dy * dy);
405
- const distance = Math.sqrt(distanceSquared);
406
- const repulsion = Math.min(9, 18_000 / distanceSquared) * cooling;
407
- const collision = distance < 66 ? (66 - distance) * .08 : 0;
408
- const force = repulsion + collision;
409
- const forceX = (dx / distance) * force;
410
- const forceY = (dy / distance) * force;
411
- leftVelocity.x += forceX;
412
- leftVelocity.y += forceY;
413
- rightVelocity.x -= forceX;
414
- rightVelocity.y -= forceY;
415
- }
416
- }
417
- for (const edge of visibleEdges) {
418
- const source = positions.get(edge.sourceId);
419
- const target = positions.get(edge.targetId);
420
- const sourceVelocity = velocities.get(edge.sourceId);
421
- const targetVelocity = velocities.get(edge.targetId);
422
- const dx = target.x - source.x;
423
- const dy = target.y - source.y;
424
- const distance = Math.max(1, Math.hypot(dx, dy));
425
- const sparseScale = nodes.length <= 3 ? 2 : nodes.length <= 8 ? 1.45 : 1;
426
- const desired = (edge.type === 'scope' ? 138 : edge.type === 'entity' ? 94 : edge.type === 'semantic' ? 118 : 106) * sparseScale;
427
- const spring = (distance - desired) * .018 * cooling;
428
- const forceX = (dx / distance) * spring;
429
- const forceY = (dy / distance) * spring;
430
- sourceVelocity.x += forceX;
431
- sourceVelocity.y += forceY;
432
- targetVelocity.x -= forceX;
433
- targetVelocity.y -= forceY;
434
- }
435
- for (const node of nodes) {
436
- const key = graphNodeKey(node);
437
- const position = positions.get(key);
438
- const velocity = velocities.get(key);
439
- const anchor = anchors.get(node.category ?? 'general') ?? { x: GRAPH_WIDTH / 2, y: GRAPH_HEIGHT / 2 };
440
- velocity.x += (anchor.x - position.x) * .0035 * cooling + (GRAPH_WIDTH / 2 - position.x) * .0008;
441
- velocity.y += (anchor.y - position.y) * .0035 * cooling + (GRAPH_HEIGHT / 2 - position.y) * .0008;
442
- velocity.x = Math.max(-12, Math.min(12, velocity.x * .76));
443
- velocity.y = Math.max(-12, Math.min(12, velocity.y * .76));
444
- positions.set(key, clampGraphPosition({ x: position.x + velocity.x, y: position.y + velocity.y }));
445
- }
446
- }
447
- return positions;
448
- }
449
- function uniformGraphPositions(nodes) {
450
- const positions = new Map();
451
- const ordered = [...nodes].sort((left, right) => {
452
- const categoryDifference = CATEGORY_ORDER.indexOf(left.category ?? 'general') - CATEGORY_ORDER.indexOf(right.category ?? 'general');
453
- return categoryDifference === 0 ? left.id.localeCompare(right.id) : categoryDifference;
454
- });
455
- const columns = Math.max(1, Math.ceil(Math.sqrt(ordered.length * 1.65)));
456
- const rows = Math.max(1, Math.ceil(ordered.length / columns));
457
- const cellWidth = (GRAPH_WIDTH - GRAPH_MARGIN_X * 2) / columns;
458
- const cellHeight = (GRAPH_HEIGHT - GRAPH_MARGIN_Y * 2) / rows;
459
- ordered.forEach((node, index) => {
460
- const row = Math.floor(index / columns);
461
- const column = index % columns;
462
- const rowLength = Math.min(columns, ordered.length - row * columns);
463
- const rowOffset = (columns - rowLength) * cellWidth / 2;
464
- positions.set(graphNodeKey(node), {
465
- x: GRAPH_MARGIN_X + rowOffset + cellWidth * (column + .5),
466
- y: GRAPH_MARGIN_Y + cellHeight * (row + .5),
467
- });
468
- });
469
- return positions;
470
- }
471
- function graphPoint(svg, clientX, clientY) {
472
- const matrix = svg.getScreenCTM?.();
473
- if (matrix !== null && matrix !== undefined && typeof svg.createSVGPoint === 'function') {
474
- const point = svg.createSVGPoint();
475
- point.x = clientX;
476
- point.y = clientY;
477
- return clampGraphPosition(point.matrixTransform(matrix.inverse()));
478
- }
479
- const bounds = svg.getBoundingClientRect();
480
- const width = bounds.width || GRAPH_WIDTH;
481
- const height = bounds.height || GRAPH_HEIGHT;
482
- return clampGraphPosition({ x: (clientX - bounds.left) * GRAPH_WIDTH / width, y: (clientY - bounds.top) * GRAPH_HEIGHT / height });
483
- }
484
- function MemoryGraph(props) {
485
- const t = useT();
486
- const visibleNodes = useMemo(() => {
487
- const spaces = props.graph.nodes.filter(node => graphNodeKind(node) === 'space');
488
- const entities = props.graph.nodes.filter(node => graphNodeKind(node) === 'entity').slice(0, 20);
489
- const memories = props.graph.nodes.filter(node => graphNodeKind(node) === 'memory').slice(0, Math.max(0, 60 - spaces.length - entities.length));
490
- return [...spaces, ...entities, ...memories].slice(0, 60);
491
- }, [props.graph.nodes]);
492
- const visibleIds = useMemo(() => new Set(visibleNodes.map(graphNodeKey)), [visibleNodes]);
493
- const visibleKinds = useMemo(() => new Map(visibleNodes.map(node => [graphNodeKey(node), graphNodeKind(node)])), [visibleNodes]);
494
- const edges = useMemo(() => {
495
- const priority = new Map([['entity', 0], ['scope', 1], ['causal', 2], ['semantic', 3], ['temporal', 4]]);
496
- return props.graph.edges
497
- .filter(edge => visibleIds.has(edge.sourceId) && visibleIds.has(edge.targetId))
498
- .map((edge, index) => ({ edge, index }))
499
- .sort((left, right) => (priority.get(left.edge.type ?? 'temporal') ?? 5) - (priority.get(right.edge.type ?? 'temporal') ?? 5) || left.index - right.index)
500
- .slice(0, 180)
501
- .map(({ edge }) => edge);
502
- }, [props.graph.edges, visibleIds]);
503
- const curvedEdges = useMemo(() => {
504
- const groups = new Map();
505
- edges.forEach((edge, index) => {
506
- const key = [edge.sourceId, edge.targetId].sort().join('::');
507
- groups.set(key, [...(groups.get(key) ?? []), index]);
508
- });
509
- return edges.map((edge, index) => {
510
- const key = [edge.sourceId, edge.targetId].sort().join('::');
511
- const group = groups.get(key) ?? [index];
512
- const groupIndex = group.indexOf(index);
513
- return { edge, offset: (groupIndex - (group.length - 1) / 2) * 12 };
514
- });
515
- }, [edges]);
516
- const layoutKey = `${visibleNodes.map(node => `${graphNodeKey(node)}:${graphNodeKind(node)}:${node.category ?? 'general'}`).join('|')}::${edges.map(edge => `${edge.sourceId}>${edge.targetId}:${edge.type ?? 'temporal'}`).join('|')}`;
517
- const naturalLayout = useMemo(() => naturalGraphPositions(visibleNodes, edges), [layoutKey]);
518
- const [positions, setPositions] = useState(() => naturalLayout);
519
- const [layoutMode, setLayoutMode] = useState('natural');
520
- const positionsRef = useRef(positions);
521
- const animationRef = useRef(null);
522
- const dragRef = useRef(null);
523
- const commitPositions = useCallback((next) => {
524
- positionsRef.current = next;
525
- setPositions(next);
526
- }, []);
527
- const cancelAnimation = useCallback(() => {
528
- if (animationRef.current !== null && typeof window.cancelAnimationFrame === 'function')
529
- window.cancelAnimationFrame(animationRef.current);
530
- animationRef.current = null;
531
- }, []);
532
- const animateTo = useCallback((target, mode) => {
533
- cancelAnimation();
534
- setLayoutMode(mode);
535
- if (typeof window.requestAnimationFrame !== 'function') {
536
- commitPositions(target);
537
- return;
538
- }
539
- const start = new Map(positionsRef.current);
540
- const startedAt = performance.now();
541
- const tick = (time) => {
542
- const progress = Math.min(1, (time - startedAt) / 620);
543
- const eased = 1 - Math.pow(1 - progress, 3);
544
- const next = new Map();
545
- for (const [id, destination] of target) {
546
- const origin = start.get(id) ?? { x: GRAPH_WIDTH / 2, y: GRAPH_HEIGHT / 2 };
547
- next.set(id, { x: origin.x + (destination.x - origin.x) * eased, y: origin.y + (destination.y - origin.y) * eased });
548
- }
549
- commitPositions(next);
550
- if (progress < 1)
551
- animationRef.current = window.requestAnimationFrame(tick);
552
- else
553
- animationRef.current = null;
554
- };
555
- animationRef.current = window.requestAnimationFrame(tick);
556
- }, [cancelAnimation, commitPositions]);
557
- useEffect(() => { animateTo(naturalLayout, 'natural'); }, [layoutKey]);
558
- useEffect(() => () => cancelAnimation(), [cancelAnimation]);
559
- const beginDrag = (event, nodeId) => {
560
- cancelAnimation();
561
- dragRef.current = { nodeId, pointerId: event.pointerId, startX: event.clientX, startY: event.clientY, moved: false };
562
- event.currentTarget.setPointerCapture?.(event.pointerId);
563
- };
564
- const moveDrag = (event) => {
565
- const drag = dragRef.current;
566
- const svg = event.currentTarget.ownerSVGElement;
567
- if (drag === null || svg === null || drag.pointerId !== event.pointerId)
568
- return;
569
- if (!drag.moved && Math.hypot(event.clientX - drag.startX, event.clientY - drag.startY) < 4)
570
- return;
571
- drag.moved = true;
572
- const point = graphPoint(svg, event.clientX, event.clientY);
573
- const next = new Map(positionsRef.current);
574
- next.set(drag.nodeId, point);
575
- commitPositions(next);
576
- setLayoutMode('custom');
577
- };
578
- const endDrag = (event) => {
579
- const drag = dragRef.current;
580
- if (drag === null || drag.pointerId !== event.pointerId)
581
- return;
582
- const svg = event.currentTarget.ownerSVGElement;
583
- if (drag.moved && svg !== null) {
584
- const next = new Map(positionsRef.current);
585
- next.set(drag.nodeId, graphPoint(svg, event.clientX, event.clientY));
586
- commitPositions(next);
587
- }
588
- dragRef.current = null;
589
- event.currentTarget.releasePointerCapture?.(event.pointerId);
590
- if (!drag.moved) {
591
- const node = visibleNodes.find(candidate => graphNodeKey(candidate) === drag.nodeId);
592
- if (node !== undefined)
593
- props.onSelect(node);
594
- }
595
- };
596
- const cancelDrag = (event) => {
597
- if (dragRef.current?.pointerId === event.pointerId)
598
- dragRef.current = null;
599
- };
600
- const nudge = (nodeId, dx, dy) => {
601
- cancelAnimation();
602
- const current = positionsRef.current.get(nodeId);
603
- if (current === undefined)
604
- return;
605
- const next = new Map(positionsRef.current);
606
- next.set(nodeId, clampGraphPosition({ x: current.x + dx, y: current.y + dy }));
607
- commitPositions(next);
608
- setLayoutMode('custom');
609
- };
610
- const layoutLabel = t(layoutMode === 'natural' ? 'graph.layoutNatural' : layoutMode === 'uniform' ? 'graph.layoutUniform' : 'graph.layoutCustom');
611
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: css.graphCanvasControls, role: "toolbar", "aria-label": t('graph.layoutAria'), children: [_jsxs("span", { role: "status", "aria-label": t('graph.layoutStatus', { layout: layoutLabel }), children: [_jsx("i", {}), t('graph.draggable', { layout: layoutLabel })] }), _jsx("button", { type: "button", "data-active": layoutMode === 'natural' || undefined, onClick: () => animateTo(naturalGraphPositions(visibleNodes, edges), 'natural'), children: t('graph.naturalAction') }), _jsx("button", { type: "button", "data-active": layoutMode === 'uniform' || undefined, onClick: () => animateTo(uniformGraphPositions(visibleNodes), 'uniform'), children: t('graph.uniformAction') })] }), _jsxs("svg", { className: css.graphSvg, viewBox: `0 0 ${GRAPH_WIDTH} ${GRAPH_HEIGHT}`, role: "img", "data-layout": layoutMode, "data-density": visibleNodes.length <= 12 ? 'sparse' : 'dense', "aria-label": t('graph.aria', { nodes: props.graph.nodes.length, edges: props.graph.edges.length }), children: [_jsxs("defs", { children: [_jsx("pattern", { id: "mnemon-grid", width: "26", height: "26", patternUnits: "userSpaceOnUse", children: _jsx("path", { d: "M 26 0 L 0 0 0 26", className: css.graphGridLine, fill: "none" }) }), _jsxs("filter", { id: "mnemon-glow", x: "-100%", y: "-100%", width: "300%", height: "300%", children: [_jsx("feGaussianBlur", { stdDeviation: "4", result: "blur" }), _jsxs("feMerge", { children: [_jsx("feMergeNode", { in: "blur" }), _jsx("feMergeNode", { in: "SourceGraphic" })] })] })] }), _jsx("rect", { width: GRAPH_WIDTH, height: GRAPH_HEIGHT, className: css.graphBackdrop }), _jsx("rect", { width: GRAPH_WIDTH, height: GRAPH_HEIGHT, fill: "url(#mnemon-grid)" }), curvedEdges.map(({ edge, offset }, index) => {
612
- const source = positions.get(edge.sourceId) ?? naturalLayout.get(edge.sourceId) ?? { x: GRAPH_WIDTH / 2, y: GRAPH_HEIGHT / 2 };
613
- const target = positions.get(edge.targetId) ?? naturalLayout.get(edge.targetId) ?? { x: GRAPH_WIDTH / 2, y: GRAPH_HEIGHT / 2 };
614
- const dx = target.x - source.x;
615
- const dy = target.y - source.y;
616
- const distance = Math.max(1, Math.hypot(dx, dy));
617
- const direction = edge.sourceId.localeCompare(edge.targetId) <= 0 ? 1 : -1;
618
- const controlX = (source.x + target.x) / 2 - (dy / distance) * offset * direction;
619
- const controlY = (source.y + target.y) / 2 + (dx / distance) * offset * direction;
620
- return _jsx("path", { d: `M ${source.x} ${source.y} Q ${controlX} ${controlY} ${target.x} ${target.y}`, className: css.graphEdge, "data-edge": edge.type ?? 'temporal', "data-source-id": edge.sourceId, "data-target-id": edge.targetId, "data-source-kind": visibleKinds.get(edge.sourceId), "data-target-kind": visibleKinds.get(edge.targetId) }, `${edge.sourceId}-${edge.targetId}-${index}`);
621
- }), visibleNodes.map((node, index) => {
622
- const nodeKey = graphNodeKey(node);
623
- const position = positions.get(nodeKey) ?? naturalLayout.get(nodeKey) ?? { x: GRAPH_WIDTH / 2, y: GRAPH_HEIGHT / 2 };
624
- const selected = props.selectedId === nodeKey;
625
- const showLabel = selected || visibleNodes.length < 22 || index % 3 === 0;
626
- return (_jsxs("g", { className: css.graphNode, "data-node-id": nodeKey, "data-category": node.category ?? 'general', "data-kind": graphNodeKind(node), "data-selected": selected || undefined, transform: `translate(${position.x} ${position.y})`, role: "button", tabIndex: 0, "aria-label": `${graphKindLabel(t, node)}: ${short(node.content, 80)}`, "data-dragging": dragRef.current?.nodeId === nodeKey || undefined, onPointerDown: event => beginDrag(event, nodeKey), onPointerMove: moveDrag, onPointerUp: endDrag, onPointerCancel: cancelDrag, onLostPointerCapture: cancelDrag, onClick: () => props.onSelect(node), onKeyDown: event => {
627
- if (event.key === 'Enter' || event.key === ' ')
628
- props.onSelect(node);
629
- else if (event.key === 'ArrowLeft') {
630
- event.preventDefault();
631
- nudge(nodeKey, -12, 0);
632
- }
633
- else if (event.key === 'ArrowRight') {
634
- event.preventDefault();
635
- nudge(nodeKey, 12, 0);
636
- }
637
- else if (event.key === 'ArrowUp') {
638
- event.preventDefault();
639
- nudge(nodeKey, 0, -12);
640
- }
641
- else if (event.key === 'ArrowDown') {
642
- event.preventDefault();
643
- nudge(nodeKey, 0, 12);
644
- }
645
- }, children: [graphNodeKind(node) === 'space'
646
- ? _jsxs(_Fragment, { children: [_jsx("rect", { x: selected ? -20 : -17, y: selected ? -15 : -13, width: selected ? 40 : 34, height: selected ? 30 : 26, rx: "9", className: css.nodeHalo, filter: selected ? 'url(#mnemon-glow)' : undefined }), _jsx("circle", { r: selected ? 6 : 5, className: css.nodeCore })] })
647
- : graphNodeKind(node) === 'entity'
648
- ? _jsxs(_Fragment, { children: [_jsx("path", { d: selected ? 'M 0 -18 L 18 0 L 0 18 L -18 0 Z' : 'M 0 -14 L 14 0 L 0 14 L -14 0 Z', className: css.nodeHalo, filter: selected ? 'url(#mnemon-glow)' : undefined }), _jsx("circle", { r: selected ? 5 : 4, className: css.nodeCore })] })
649
- : _jsxs(_Fragment, { children: [_jsx("circle", { r: selected ? 17 : visibleNodes.length <= 12 ? 14 : 11, className: css.nodeHalo, filter: selected ? 'url(#mnemon-glow)' : undefined }), _jsx("circle", { r: selected ? 7 : visibleNodes.length <= 12 ? 6 : 4.5, className: css.nodeCore })] }), (selected || visibleNodes.length <= 12) && graphNodeKind(node) === 'memory' && node.memoryBodyName !== undefined && _jsx("text", { x: "0", y: "-18", textAnchor: "middle", className: css.nodeBodyLabel, children: short(node.memoryBodyName, 12) }), showLabel && _jsx("text", { x: visibleNodes.length <= 12 ? 19 : 15, y: "4", className: css.nodeLabel, children: short(node.content.replace(/\s+/gu, ' '), selected ? 34 : visibleNodes.length <= 12 ? 26 : 19) })] }, nodeKey));
650
- })] })] }));
651
- }
652
- function OverviewPage(props) {
653
- const t = useT();
654
- const locale = useLocale();
655
- const appearance = useMnemonViewAppearance();
656
- const [graph, setGraph] = useState(null);
657
- const [catalog, setCatalog] = useState(null);
658
- const [selected, setSelected] = useState(null);
659
- const [loading, setLoading] = useState(true);
660
- const [error, setError] = useState(null);
661
- const [changing, setChanging] = useState(null);
662
- const [creating, setCreating] = useState(false);
663
- const [creatingBodyOpen, setCreatingBodyOpen] = useState(false);
664
- const [bodyName, setBodyName] = useState('');
665
- const [bodyDescription, setBodyDescription] = useState('');
666
- const [catalogUnavailable, setCatalogUnavailable] = useState(false);
667
- const [editingBody, setEditingBody] = useState(null);
668
- const [editName, setEditName] = useState('');
669
- const [editDescription, setEditDescription] = useState('');
670
- const [savingBody, setSavingBody] = useState(null);
671
- const [confirmingDeleteBody, setConfirmingDeleteBody] = useState(null);
672
- const [deletingBody, setDeletingBody] = useState(null);
673
- const [preview, setPreview] = useState(null);
674
- const load = useCallback(async (quiet = false) => {
675
- if (!quiet)
676
- setLoading(true);
677
- setError(null);
678
- try {
679
- const [nextCatalog, next] = await Promise.all([
680
- props.client.bodies().then(next => { setCatalogUnavailable(false); return next; }).catch(() => {
681
- setCatalogUnavailable(!props.catalogKnown);
682
- return {
683
- items: props.fallbackBodies,
684
- total: props.fallbackBodies.length,
685
- activeCount: props.fallbackBodies.filter(body => body.active).length,
686
- directory: props.fallbackDirectory ?? '',
687
- generatedAt: new Date().toISOString(),
688
- };
689
- }),
690
- props.client.graph(),
691
- ]);
692
- const enriched = enrichMultiSpaceGraph(next, nextCatalog.items);
693
- setCatalog(nextCatalog);
694
- setGraph(enriched);
695
- setSelected(current => current === null ? null : enriched.nodes.find(node => graphNodeKey(node) === graphNodeKey(current)) ?? null);
696
- }
697
- catch (reason) {
698
- setError(message(reason));
699
- }
700
- finally {
701
- setLoading(false);
702
- }
703
- }, [props.catalogKnown, props.client, props.fallbackBodies, props.fallbackDirectory]);
704
- useEffect(() => {
705
- void load();
706
- const timer = window.setInterval(() => void load(true), 15_000);
707
- return () => window.clearInterval(timer);
708
- }, [load, props.revision]);
709
- const toggle = async (body) => {
710
- setChanging(body.id);
711
- setError(null);
712
- try {
713
- await props.client.updateBody(body.id, { active: !body.active });
714
- await load(true);
715
- props.onMutate();
716
- }
717
- catch (reason) {
718
- setError(message(reason));
719
- }
720
- finally {
721
- setChanging(null);
722
- }
723
- };
724
- const beginEdit = (body) => {
725
- setEditingBody(body.id);
726
- setEditName(body.name);
727
- setEditDescription(body.description ?? '');
728
- setError(null);
729
- };
730
- const saveEdit = async (event, body) => {
731
- event.preventDefault();
732
- if (editName.trim() === '')
733
- return;
734
- setSavingBody(body.id);
735
- setError(null);
736
- try {
737
- await props.client.updateBody(body.id, { name: editName, description: editDescription });
738
- setEditingBody(null);
739
- await load(true);
740
- props.onMutate();
741
- }
742
- catch (reason) {
743
- setError(message(reason));
744
- }
745
- finally {
746
- setSavingBody(null);
747
- }
748
- };
749
- const create = async (event) => {
750
- event.preventDefault();
751
- if (bodyName.trim() === '' || bodyDescription.trim() === '')
752
- return;
753
- setCreating(true);
754
- setError(null);
755
- try {
756
- await props.client.createBody({ name: bodyName, description: bodyDescription });
757
- setBodyName('');
758
- setBodyDescription('');
759
- if (appearance.surface === 'sidebar')
760
- setCreatingBodyOpen(false);
761
- await load(true);
762
- props.onMutate();
763
- }
764
- catch (reason) {
765
- setError(message(reason));
766
- }
767
- finally {
768
- setCreating(false);
769
- }
770
- };
771
- const deleteBody = async (body) => {
772
- setDeletingBody(body.id);
773
- setError(null);
774
- try {
775
- await props.client.deleteBody(body.id);
776
- setConfirmingDeleteBody(null);
777
- await load(true);
778
- props.onMutate();
779
- }
780
- catch (reason) {
781
- setError(message(reason));
782
- }
783
- finally {
784
- setDeletingBody(null);
785
- }
786
- };
787
- const generated = graph === null ? t('overview.waitingSnapshot') : t('overview.updatedAt', { time: new Date(graph.generatedAt).toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit', second: '2-digit' }) });
788
- const graphSpaces = graph?.nodes.filter(node => graphNodeKind(node) === 'space').length ?? 0;
789
- const graphEntities = graph?.nodes.filter(node => graphNodeKind(node) === 'entity').length ?? 0;
790
- const graphMemories = graph?.nodes.filter(node => graphNodeKind(node) === 'memory').length ?? 0;
791
- const selectedKind = selected === null ? null : graphNodeKind(selected);
792
- const editingBodyView = editingBody === null ? undefined : catalog?.items.find(body => body.id === editingBody);
793
- const deletingBodyView = confirmingDeleteBody === null ? undefined : catalog?.items.find(body => body.id === confirmingDeleteBody);
794
- const bodyEditForm = (body) => _jsxs("form", { className: css.bodyEdit, onSubmit: event => void saveEdit(event, body), children: [_jsxs("label", { children: [t('overview.editName'), _jsx("input", { "aria-label": t('overview.editName'), value: editName, onChange: event => setEditName(event.target.value), maxLength: 100, required: true })] }), _jsxs("label", { children: [t('overview.editDescription'), _jsx("textarea", { "aria-label": t('overview.editDescription'), value: editDescription, onChange: event => setEditDescription(event.target.value), rows: 4, maxLength: 1000 })] }), _jsxs("div", { className: css.bodyEditActions, children: [appearance.surface === 'sidebar' && _jsx("button", { type: "button", className: css.ghostButton, disabled: savingBody === body.id, onClick: () => setEditingBody(null), children: t('common.cancel') }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: savingBody === body.id || editName.trim() === '', children: savingBody === body.id ? t('overview.savingBody') : t('overview.saveBody') }), appearance.surface === 'buildin' && _jsx("button", { type: "button", className: css.ghostButton, onClick: () => setEditingBody(null), children: t('common.cancel') })] })] });
795
- const bodyCreateForm = _jsxs("form", { className: css.bodyEdit, onSubmit: event => void create(event), children: [_jsxs("label", { children: [t('overview.createName'), _jsx("input", { "aria-label": t('overview.createName'), value: bodyName, onChange: event => setBodyName(event.target.value), placeholder: t('overview.createNamePlaceholder'), maxLength: 100, required: true })] }), _jsxs("label", { children: [t('overview.createDescription'), _jsx("textarea", { "aria-label": t('overview.createDescription'), value: bodyDescription, onChange: event => setBodyDescription(event.target.value), placeholder: t('overview.createDescriptionPlaceholder'), rows: 5, maxLength: 1000, required: true })] }), _jsxs("div", { className: css.bodyEditActions, children: [_jsx("button", { type: "button", className: css.ghostButton, disabled: creating, onClick: () => setCreatingBodyOpen(false), children: t('common.cancel') }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: creating || bodyName.trim() === '' || bodyDescription.trim() === '', children: creating ? t('overview.creating') : t('overview.createAction') })] })] });
796
- const bodyToggle = (body) => _jsxs("button", { type: "button", className: css.bodySwitch, role: "switch", "aria-checked": body.active, "aria-label": t('overview.toggleAria', { name: body.name }), disabled: !props.writeEnabled || changing === body.id || deletingBody === body.id, onClick: () => void toggle(body), children: [_jsx("span", { className: css.bodySwitchTrack, "aria-hidden": "true", children: _jsx("i", {}) }), _jsx("span", { children: changing === body.id ? t('overview.toggling') : body.active ? t('common.active') : t('common.inactive') })] });
797
- const bodyEditActionClass = appearanceClass(css.ghostButton, appearanceClass(appearance.classes.itemActionButton, appearance.classes.itemEditAction));
798
- const bodyDeleteActionClass = appearanceClass(css.dangerButton, appearanceClass(appearance.classes.itemActionButton, appearance.classes.itemDangerAction));
799
- return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: appearance.surface === 'sidebar' ? t('nav.overview') : t('overview.title'), description: t(appearance.surface === 'sidebar' ? 'overview.pageDescription' : 'overview.description'), meta: t('overview.interval'), action: _jsx("button", { type: "button", className: css.secondaryButton, disabled: loading, onClick: () => void load(), children: loading ? t('overview.syncing') : t('overview.syncNow') }) }), error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), _jsxs("section", { className: css.bodyDirectory, "aria-label": t('overview.directory'), children: [_jsxs("div", { className: css.bodyDirectoryHeader, children: [_jsxs("div", { children: [_jsx("h3", { children: t('overview.directory') }), _jsx("p", { children: t('overview.directory.description') }), _jsx("code", { className: css.bodyDirectoryPath, children: catalogUnavailable ? t('overview.directory.unsynced') : catalog?.directory || props.fallbackDirectory || t('overview.directory.waiting') })] }), appearance.surface === 'sidebar' ? _jsxs("div", { className: appearance.classes.bodyDirectoryActions, children: [_jsx("strong", { children: catalogUnavailable ? t('overview.directory.unsyncedBadge') : `${catalog?.activeCount ?? '—'} / ${catalog?.total ?? '—'} ${t('common.active')}` }), props.writeEnabled && !catalogUnavailable && _jsx("button", { type: "button", className: bodyEditActionClass, onClick: () => setCreatingBodyOpen(true), children: t('overview.createTitle') })] }) : _jsx("strong", { children: catalogUnavailable ? t('overview.directory.unsyncedBadge') : `${catalog?.activeCount ?? '—'} / ${catalog?.total ?? '—'} ${t('common.active')}` })] }), _jsxs("div", { className: css.bodyGrid, children: [catalog?.items.map(body => (_jsx("article", { className: css.bodyCard, "data-active": body.active || undefined, "data-healthy": body.healthy || undefined, "data-editing": (appearance.surface === 'buildin' && editingBody === body.id) || undefined, title: body.error, children: appearance.surface === 'buildin'
800
- ? editingBody === body.id
801
- ? bodyEditForm(body)
802
- : _jsxs(_Fragment, { children: [_jsxs("div", { className: css.bodyCardTop, children: [_jsx("span", { className: css.bodySignal }), _jsxs("div", { children: [_jsx("strong", { children: body.name }), _jsx("code", { children: body.id }), _jsx("small", { className: css.bodyHealth, children: body.healthy ? t('overview.storageHealthy') : t('overview.storageUnhealthy') })] }), _jsxs("div", { className: css.bodyCardActions, children: [bodyToggle(body), _jsx("button", { type: "button", className: css.bodyEditButton, "aria-label": t('overview.editBodyAria', { name: body.name }), title: t('overview.editBody'), disabled: !props.writeEnabled, onClick: () => beginEdit(body), children: "\u270E" })] })] }), _jsx("p", { children: body.description || t('overview.noDescription') }), _jsxs("footer", { children: [_jsx("span", { children: t('common.memories', { count: body.stats?.totalInsights ?? 0 }) }), _jsx("span", { children: t('common.edges', { count: body.stats?.edgeCount ?? 0 }) }), _jsx("span", { children: humanBytes(body.stats?.dbSizeBytes ?? 0) })] })] })
803
- : _jsxs(_Fragment, { children: [_jsxs("div", { className: appearance.classes.bodyCardHeader, children: [_jsxs("div", { className: appearance.classes.bodyCardIdentity, children: [_jsx("span", { className: css.bodySignal }), _jsxs("div", { children: [_jsx("strong", { children: body.name }), _jsxs("div", { className: appearance.classes.bodyCardMeta, children: [_jsx("code", { children: body.id }), _jsx("small", { className: css.bodyHealth, children: body.healthy ? t('overview.storageHealthy') : t('overview.storageUnhealthy') })] })] })] }), bodyToggle(body)] }), _jsx("p", { title: body.description || t('overview.noDescription'), children: body.description || t('overview.noDescription') }), _jsxs("footer", { className: appearance.classes.bodyCardFooter, children: [_jsxs("div", { className: appearance.classes.bodyCardStats, children: [_jsx("span", { children: t('common.memories', { count: body.stats?.totalInsights ?? 0 }) }), _jsx("span", { children: t('common.edges', { count: body.stats?.edgeCount ?? 0 }) }), _jsx("span", { children: humanBytes(body.stats?.dbSizeBytes ?? 0) })] }), _jsxs("div", { className: css.bodyCardActions, children: [_jsx("button", { type: "button", className: bodyEditActionClass, "aria-label": t('overview.editBodyAria', { name: body.name }), disabled: !props.writeEnabled || deletingBody === body.id, onClick: () => beginEdit(body), children: t('overview.editBody') }), _jsx("button", { type: "button", className: bodyDeleteActionClass, "aria-label": t('overview.deleteBodyAria', { name: body.name }), disabled: !props.writeEnabled || deletingBody === body.id, onClick: () => setConfirmingDeleteBody(body.id), children: t('overview.deleteBody') })] })] })] }) }, body.id))), catalog?.total === 0 && _jsxs("div", { className: css.bodyDirectoryEmpty, children: [_jsx("span", { children: "\u25C7" }), _jsxs("div", { children: [_jsx("strong", { children: catalogUnavailable ? t('overview.unsyncedTitle') : t('overview.emptyTitle') }), _jsx("p", { children: catalogUnavailable ? t('overview.unsyncedShort') : t('overview.emptyShort') })] })] })] }), appearance.surface === 'buildin' && props.writeEnabled && !catalogUnavailable && _jsxs("details", { className: css.bodyCreate, open: catalog?.total === 0 ? true : undefined, children: [_jsx("summary", { children: t('overview.create') }), _jsxs("form", { onSubmit: event => void create(event), children: [_jsx("input", { "aria-label": t('overview.createName'), value: bodyName, onChange: event => setBodyName(event.target.value), placeholder: t('overview.createNamePlaceholder'), required: true }), _jsx("input", { "aria-label": t('overview.createDescription'), value: bodyDescription, onChange: event => setBodyDescription(event.target.value), placeholder: t('overview.createDescriptionPlaceholder'), required: true }), _jsx("button", { type: "submit", className: css.secondaryButton, disabled: creating, children: creating ? t('overview.creating') : t('overview.createAction') })] })] })] }), appearance.surface === 'sidebar' && creatingBodyOpen && _jsx(SidebarModal, { title: t('overview.createTitle'), description: catalog?.directory || props.fallbackDirectory || t('overview.directory.waiting'), busy: creating, onClose: () => setCreatingBodyOpen(false), children: bodyCreateForm }), appearance.surface === 'sidebar' && editingBodyView !== undefined && _jsx(SidebarModal, { title: t('overview.editBodyAria', { name: editingBodyView.name }), description: editingBodyView.id, busy: savingBody === editingBodyView.id, onClose: () => setEditingBody(null), children: bodyEditForm(editingBodyView) }), appearance.surface === 'sidebar' && deletingBodyView !== undefined && _jsx(SidebarModal, { title: t('overview.deleteTitle', { name: deletingBodyView.name }), description: deletingBodyView.id, busy: deletingBody === deletingBodyView.id, onClose: () => setConfirmingDeleteBody(null), children: _jsxs("div", { className: css.bodyDeleteConfirm, children: [_jsx("p", { children: t('overview.deleteWarning') }), _jsxs("div", { className: css.bodyDeleteSummary, children: [_jsx("strong", { children: deletingBodyView.name }), _jsxs("span", { children: [t('common.memories', { count: deletingBodyView.stats?.totalInsights ?? 0 }), " \u00B7 ", t('common.edges', { count: deletingBodyView.stats?.edgeCount ?? 0 }), " \u00B7 ", humanBytes(deletingBodyView.stats?.dbSizeBytes ?? 0)] })] }), _jsxs("div", { className: css.bodyEditActions, children: [_jsx("button", { type: "button", "data-autofocus": true, className: css.ghostButton, disabled: deletingBody === deletingBodyView.id, onClick: () => setConfirmingDeleteBody(null), children: t('common.cancel') }), _jsx("button", { type: "button", className: css.dangerSolidButton, disabled: deletingBody === deletingBodyView.id, onClick: () => void deleteBody(deletingBodyView), children: deletingBody === deletingBodyView.id ? t('overview.deletingBody') : t('overview.deleteAction') })] })] }) }), !catalogUnavailable && graph !== null && graph.nodes.length > 0 ? (_jsxs("div", { className: css.graphLayout, children: [_jsxs("section", { className: css.graphPanel, children: [_jsxs("div", { className: css.graphToolbar, children: [_jsxs("div", { children: [_jsx("span", { className: css.liveDot }), t('overview.snapshot'), " ", _jsx("small", { children: generated })] }), _jsxs("div", { className: css.graphLegend, children: [_jsx("span", { "data-edge": "scope", children: t('overview.edgeScope') }), _jsx("span", { "data-edge": "temporal", children: t('overview.edgeTemporal') }), _jsx("span", { "data-edge": "semantic", children: t('overview.edgeSemantic') }), _jsx("span", { "data-edge": "causal", children: t('overview.edgeCausal') }), _jsx("span", { "data-edge": "entity", children: t('overview.edgeEntity') })] })] }), _jsx("div", { className: css.graphViewport, children: _jsx(MemoryGraph, { graph: graph, selectedId: selected === null ? undefined : graphNodeKey(selected), onSelect: setSelected }) }), _jsxs("div", { className: css.graphFooter, children: [_jsx("span", { children: t('overview.graphComposition', { spaces: graphSpaces, memories: graphMemories, entities: graphEntities }) }), _jsxs("span", { children: [t('overview.graphCount', { visible: Math.min(graph.nodes.length, 60), total: graph.nodes.length }), " \u00B7 ", t('overview.graphEdges', { count: graph.edges.length })] })] })] }), _jsx("aside", { className: css.graphInspector, "data-empty": selected === null || undefined, children: selected === null ? (_jsxs("div", { className: css.inspectorEmpty, children: [appearance.showLogo ? _jsx(MnemonLogo, { className: css.inspectorLogo, title: t('overview.inspector') }) : _jsx("span", { className: appearanceClass(css.inspectorLogo, appearance.classes.inspectorGlyph), "aria-hidden": "true", children: "\u25C7" }), _jsx("h3", { children: t('overview.selectNode') }), _jsx("p", { children: t('overview.selectNodeText') })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: css.inspectorHeading, children: [_jsx("span", { children: t(selectedKind === 'space' ? 'overview.inspectorSpace' : selectedKind === 'entity' ? 'overview.inspectorEntity' : 'overview.inspector') }), _jsx("button", { type: "button", onClick: () => setSelected(null), "aria-label": t('overview.closeInspector'), children: "\u00D7" })] }), _jsx("span", { className: css.categoryChip, children: graphKindLabel(t, selected) }), _jsxs("div", { className: css.inspectorTitleRow, children: [_jsx("h3", { className: css.inspectorTitle, children: selected.content }), selectedKind === 'memory' && selected.content.length > 140 && _jsx("button", { type: "button", className: css.inspectorEye, onClick: () => setPreview(selected), "aria-label": t('overview.previewAria'), title: t('overview.previewAria'), children: _jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", "aria-hidden": "true", children: [_jsx("path", { d: "M1 8s2.6-4.4 7-4.4S15 8 15 8s-2.6 4.4-7 4.4S1 8 1 8z", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }), _jsx("circle", { cx: "8", cy: "8", r: "2.1", fill: "currentColor" })] }) })] }), selectedKind === 'space'
804
- ? _jsxs("dl", { className: css.inspectorMeta, children: [_jsxs("div", { children: [_jsx("dt", { children: t('overview.spaceId') }), _jsx("dd", { children: _jsx("code", { children: selected.memoryBodyId ?? selected.id }) })] }), _jsxs("div", { children: [_jsx("dt", { children: t('overview.containedMemories') }), _jsx("dd", { children: selected.occurrenceCount ?? 0 })] })] })
805
- : selectedKind === 'entity'
806
- ? _jsxs("dl", { className: css.inspectorMeta, children: [_jsxs("div", { children: [_jsx("dt", { children: t('overview.entityMentions') }), _jsx("dd", { children: selected.occurrenceCount ?? 0 })] }), _jsxs("div", { children: [_jsx("dt", { children: t('term.spaces') }), _jsx("dd", { children: selected.memoryBodyNames?.join(' · ') || '—' })] })] })
807
- : _jsxs("dl", { className: css.inspectorMeta, children: [_jsxs("div", { children: [_jsx("dt", { children: t('term.space') }), _jsxs("dd", { children: [selected.memoryBodyName ?? '—', " ", _jsx("code", { children: selected.memoryBodyId ?? '' })] })] }), _jsxs("div", { children: [_jsx("dt", { children: t('overview.memoryId') }), _jsx("dd", { children: _jsx("code", { children: selected.id }) })] }), _jsxs("div", { children: [_jsx("dt", { children: t('common.category') }), _jsx("dd", { children: categoryLabel(t, selected.category ?? 'general') })] })] }), _jsxs("div", { className: css.inspectorActions, children: [selectedKind !== 'space' && _jsx("button", { type: "button", className: css.primaryButton, onClick: () => props.onExplore(selected.content), children: t('overview.exploreNode') }), _jsx("button", { type: "button", className: css.secondaryButton, onClick: () => void navigator.clipboard?.writeText(selected.id), children: t('common.copyId') })] })] })) })] })) : !loading && error === null ? (catalogUnavailable
808
- ? _jsx(EmptyState, { glyph: "\u25C7", title: t('overview.unsyncedTitle'), children: t('overview.unsyncedLong') })
809
- : catalog?.total === 0
810
- ? _jsx(EmptyState, { glyph: "\u25C7", title: t('overview.emptyTitle'), children: t('overview.emptyLong') })
811
- : catalog?.activeCount === 0
812
- ? _jsx(EmptyState, { glyph: "\u25C7", title: t('overview.noActiveTitle'), children: t('overview.noActiveText') })
813
- : _jsx(EmptyState, { glyph: "\u25C7", title: t('overview.noContentTitle'), children: t('overview.noContentText') })) : (_jsx("div", { className: css.loadingPanel, children: t('overview.loading') })), preview !== null && _jsx(ContentPreview, { node: preview, kind: graphKindLabel(t, preview), onClose: () => setPreview(null) })] }));
814
- }
815
- function ExplorePage(props) {
816
- const t = useT();
817
- const appearance = useMnemonViewAppearance();
818
- const pageSize = appearance.surface === 'sidebar' ? 6 : Number.MAX_SAFE_INTEGER;
819
- const [query, setQuery] = useState(props.seed);
820
- const [mode, setMode] = useState('smart');
821
- const [category, setCategory] = useState('');
822
- const [results, setResults] = useState([]);
823
- const [searchKind, setSearchKind] = useState(null);
824
- const [agentAnswer, setAgentAnswer] = useState(null);
825
- const [searched, setSearched] = useState(false);
826
- const [error, setError] = useState(null);
827
- const [relatedTo, setRelatedTo] = useState(null);
828
- const [related, setRelated] = useState([]);
829
- const [relatedLoading, setRelatedLoading] = useState(false);
830
- const [visibleResultLimit, setVisibleResultLimit] = useState(pageSize);
831
- const [visibleRelatedLimit, setVisibleRelatedLimit] = useState(pageSize);
832
- useEffect(() => { if (props.seed !== '')
833
- setQuery(props.seed); }, [props.seed]);
834
- const runSearch = async (withAgent) => {
835
- if (query.trim() === '')
836
- return;
837
- setSearchKind(withAgent ? 'agent' : 'direct');
838
- setSearched(true);
839
- setError(null);
840
- setRelatedTo(null);
841
- setAgentAnswer(null);
842
- setVisibleResultLimit(pageSize);
843
- setVisibleRelatedLimit(pageSize);
844
- try {
845
- const request = { query, mode, ...(category === '' ? {} : { category }), limit: props.status?.defaultRecallLimit ?? 10 };
846
- if (withAgent) {
847
- const response = await props.client.agentSearch(request);
848
- setResults(response.results);
849
- setAgentAnswer({ answer: response.answer, citations: response.citations, runId: response.delegation.runId });
850
- }
851
- else {
852
- setResults((await props.client.search(request)).results);
853
- }
854
- }
855
- catch (reason) {
856
- setError(message(reason));
857
- setResults([]);
858
- setAgentAnswer(null);
859
- }
860
- finally {
861
- setSearchKind(null);
862
- }
863
- };
864
- const search = (event) => { event.preventDefault(); void runSearch(false); };
865
- const searching = searchKind !== null;
866
- const showRelated = async (insight) => {
867
- setRelatedTo(insight);
868
- setRelated([]);
869
- setRelatedLoading(true);
870
- setError(null);
871
- setVisibleRelatedLimit(pageSize);
872
- if (typeof window.requestAnimationFrame === 'function')
873
- window.requestAnimationFrame(() => document.getElementById('mnemon-related-pane')?.scrollIntoView({ block: 'nearest', behavior: 'smooth' }));
874
- try {
875
- setRelated(await props.client.related(insight.id, insight.memoryBodyId));
876
- }
877
- catch (reason) {
878
- setError(message(reason));
879
- }
880
- finally {
881
- setRelatedLoading(false);
882
- }
883
- };
884
- const forget = async (insight) => {
885
- await props.onForget(insight);
886
- setResults(items => items.filter(item => insightKey(item) !== insightKey(insight)));
887
- setRelated(items => items.filter(item => insightKey(item) !== insightKey(insight)));
888
- if (relatedTo !== null && insightKey(relatedTo) === insightKey(insight))
889
- setRelatedTo(null);
890
- };
891
- const visibleResults = results.slice(0, visibleResultLimit);
892
- const visibleRelated = related.slice(0, visibleRelatedLimit);
893
- return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('search.title'), description: t('search.description'), meta: t('search.maxResults', { count: props.status?.defaultRecallLimit ?? '—' }) }), _jsxs("form", { className: css.searchBar, onSubmit: event => void search(event), children: [_jsxs("div", { className: css.queryField, children: [_jsx("span", { "aria-hidden": "true", children: "\u2315" }), _jsx("input", { value: query, onChange: event => setQuery(event.target.value), placeholder: t('search.placeholder'), "aria-label": t('search.queryAria') }), _jsx("kbd", { children: "\u21B5" })] }), _jsxs("div", { className: css.searchControls, children: [_jsxs("label", { children: [t('common.category'), _jsxs("select", { value: category, onChange: event => setCategory(event.target.value), "aria-label": t('search.categoryAria'), children: [_jsx("option", { value: "", children: t('common.allCategories') }), CATEGORIES.map(value => _jsx("option", { value: value, children: categoryLabel(t, value) }, value))] })] }), _jsxs("label", { children: [t('search.strategy'), _jsxs("select", { value: mode, onChange: event => setMode(event.target.value), "aria-label": t('search.modeAria'), children: [_jsx("option", { value: "smart", children: t('search.modeSmart') }), _jsx("option", { value: "keyword", children: t('search.modeKeyword') }), _jsx("option", { value: "basic", children: t('search.modeBasic') })] })] }), _jsxs("div", { className: css.searchActions, children: [_jsx("button", { type: "submit", className: css.secondaryButton, disabled: searching || query.trim() === '', children: searchKind === 'direct' ? t('search.searching') : t('search.action') }), _jsx("button", { type: "button", className: css.primaryButton, disabled: searching || query.trim() === '' || props.status?.lifecycle?.sessionAvailable !== true, onClick: () => void runSearch(true), children: searchKind === 'agent' ? t('search.agentSearching') : t('search.agentAction') })] })] })] }), agentAnswer !== null && _jsxs("section", { className: css.agentAnswer, "aria-label": t('search.agentAnswer'), children: [_jsxs("div", { className: css.agentAnswerHeading, children: [_jsxs("div", { children: [_jsx("span", { children: t('search.agentAnswerHint') }), _jsx("h3", { children: t('search.agentAnswer') })] }), _jsx("code", { children: agentAnswer.runId.slice(0, 8) })] }), _jsx("p", { children: agentAnswer.answer }), agentAnswer.citations.length > 0 && _jsx("div", { className: css.agentCitations, children: agentAnswer.citations.map(citation => _jsx("code", { children: citation }, citation)) })] }), error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), !searched && _jsx(EmptyState, { glyph: "\u2315", title: t('search.startTitle'), children: t('search.startText') }), searched && !searching && results.length === 0 && error === null && _jsx(EmptyState, { glyph: "0", title: t('search.emptyTitle'), children: t('search.emptyText') }), results.length > 0 && (_jsxs("div", { className: relatedTo === null ? css.singleColumn : css.resultLayout, children: [_jsxs("section", { className: css.results, children: [_jsxs("div", { className: css.sectionHeading, children: [_jsx("div", { children: _jsx("h3", { children: t('search.results') }) }), _jsx("strong", { children: results.length })] }), visibleResults.map(insight => _jsx(InsightCard, { insight: insight, writeEnabled: props.writeEnabled, onForget: forget, onRelated: item => void showRelated(item) }, insightKey(insight))), appearance.surface === 'sidebar' && _jsx(ProgressiveFooter, { visible: visibleResults.length, total: results.length, pageSize: pageSize, onMore: () => setVisibleResultLimit(value => value + pageSize) })] }), relatedTo !== null && _jsxs("aside", { id: "mnemon-related-pane", className: css.relatedPane, children: [_jsxs("div", { className: css.sectionHeading, children: [_jsx("div", { children: _jsx("h3", { children: t('search.related') }) }), _jsx("button", { type: "button", onClick: () => setRelatedTo(null), "aria-label": t('search.closeRelated'), children: "\u00D7" })] }), _jsx("p", { className: css.relatedSource, children: relatedTo.content }), relatedLoading && _jsx("div", { className: css.loading, children: t('search.traversing') }), !relatedLoading && related.length === 0 && _jsx("div", { className: css.muted, children: t('search.noRelated') }), visibleRelated.map(insight => _jsx(InsightCard, { insight: insight, writeEnabled: props.writeEnabled, onForget: forget, onRelated: item => void showRelated(item) }, insightKey(insight))), appearance.surface === 'sidebar' && !relatedLoading && _jsx(ProgressiveFooter, { visible: visibleRelated.length, total: related.length, pageSize: pageSize, onMore: () => setVisibleRelatedLimit(value => value + pageSize) })] })] }))] }));
894
- }
895
- function EntitiesPage(props) {
896
- const t = useT();
897
- const appearance = useMnemonViewAppearance();
898
- const entityPageSize = appearance.surface === 'sidebar' ? 10 : Number.MAX_SAFE_INTEGER;
899
- const insightPageSize = appearance.surface === 'sidebar' ? 6 : Number.MAX_SAFE_INTEGER;
900
- const [view, setView] = useState({ items: [], insights: [] });
901
- const [entity, setEntity] = useState('');
902
- const [loading, setLoading] = useState(true);
903
- const [error, setError] = useState(null);
904
- const [visibleEntityLimit, setVisibleEntityLimit] = useState(entityPageSize);
905
- const [visibleInsightLimit, setVisibleInsightLimit] = useState(insightPageSize);
906
- const load = useCallback(async (selected) => {
907
- setLoading(true);
908
- setError(null);
909
- setVisibleInsightLimit(insightPageSize);
910
- if (selected === undefined)
911
- setVisibleEntityLimit(entityPageSize);
912
- try {
913
- setView(await props.client.entities(selected, 20));
914
- }
915
- catch (reason) {
916
- setError(message(reason));
917
- }
918
- finally {
919
- setLoading(false);
920
- }
921
- }, [entityPageSize, insightPageSize, props.client]);
922
- useEffect(() => { void load(); }, [load, props.revision]);
923
- const submit = (event) => { event.preventDefault(); if (entity.trim() !== '')
924
- void load(entity); };
925
- const visibleEntities = view.items.slice(0, visibleEntityLimit);
926
- const visibleInsights = view.insights.slice(0, visibleInsightLimit);
927
- return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('entities.title'), description: t('entities.description'), meta: t('entities.count', { count: view.items.length }) }), _jsxs("div", { className: css.entityLayout, children: [_jsxs("aside", { className: css.entityRail, children: [_jsxs("form", { className: css.entitySearch, onSubmit: submit, children: [_jsx("input", { "aria-label": t('entities.nameAria'), value: entity, onChange: event => setEntity(event.target.value), placeholder: t('entities.placeholder') }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: loading || entity.trim() === '', children: t('entities.action') })] }), _jsxs("div", { className: css.entityHeading, children: [_jsx("span", { children: t('entities.top') }), _jsx("small", { children: t('entities.frequency') })] }), _jsx("div", { className: css.entityList, children: visibleEntities.map(item => _jsxs("button", { type: "button", "aria-pressed": view.selected === item.entity, onClick: () => { setEntity(item.entity); void load(item.entity); }, children: [_jsx("span", { children: item.entity }), _jsx("strong", { children: item.count })] }, item.entity)) }), appearance.surface === 'sidebar' && _jsx(ProgressiveFooter, { compact: true, visible: visibleEntities.length, total: view.items.length, pageSize: entityPageSize, onMore: () => setVisibleEntityLimit(value => value + entityPageSize) }), !loading && view.items.length === 0 && _jsx("p", { className: css.muted, children: t('entities.emptyRail') })] }), _jsxs("section", { className: css.entityResults, children: [error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), loading && _jsx("div", { className: css.loadingPanel, children: t('entities.loading') }), !loading && view.selected === undefined && _jsx(EmptyState, { glyph: "\u25CE", title: t('entities.selectTitle'), children: t('entities.selectText') }), !loading && view.selected !== undefined && _jsxs(_Fragment, { children: [_jsxs("div", { className: css.sectionHeading, children: [_jsx("div", { children: _jsx("h3", { children: view.selected }) }), _jsx("strong", { children: view.insights.length })] }), view.insights.length === 0 ? _jsx(EmptyState, { glyph: "0", title: t('entities.emptyTitle'), children: t('entities.emptyText') }) : _jsxs(_Fragment, { children: [visibleInsights.map(insight => _jsx(InsightCard, { insight: insight, writeEnabled: props.writeEnabled, onForget: props.onForget, onRelated: () => props.onExplore(insight.content) }, insightKey(insight))), appearance.surface === 'sidebar' && _jsx(ProgressiveFooter, { visible: visibleInsights.length, total: view.insights.length, pageSize: insightPageSize, onMore: () => setVisibleInsightLimit(value => value + insightPageSize) })] })] })] })] })] }));
928
- }
929
- function RuntimePage(props) {
930
- const t = useT();
931
- const locale = useLocale();
932
- const appearance = useMnemonViewAppearance();
933
- const pageSize = 10;
934
- const [snapshot, setSnapshot] = useState(null);
935
- const [loading, setLoading] = useState(true);
936
- const [error, setError] = useState(null);
937
- const [notice, setNotice] = useState(null);
938
- const [target, setTarget] = useState('memory');
939
- const [importance, setImportance] = useState('normal');
940
- const [content, setContent] = useState('');
941
- const [saving, setSaving] = useState(false);
942
- const [editing, setEditing] = useState(null);
943
- const [editContent, setEditContent] = useState('');
944
- const [editImportance, setEditImportance] = useState('normal');
945
- const [removing, setRemoving] = useState(null);
946
- const [adding, setAdding] = useState(false);
947
- const [filterTarget, setFilterTarget] = useState('all');
948
- const [filterQuery, setFilterQuery] = useState('');
949
- const [visibleLimit, setVisibleLimit] = useState(pageSize);
950
- const load = useCallback(async () => {
951
- setLoading(true);
952
- setError(null);
953
- try {
954
- setSnapshot(await props.client.runtimeMemory());
955
- }
956
- catch (reason) {
957
- setError(message(reason));
958
- }
959
- finally {
960
- setLoading(false);
961
- }
962
- }, [props.client]);
963
- useEffect(() => { void load(); }, [load, props.revision]);
964
- useEffect(() => { setVisibleLimit(pageSize); }, [filterQuery, filterTarget]);
965
- const entryKey = (entry) => `${entry.target}:${entry.created_at}:${entry.content}`;
966
- const mutate = async (request) => {
967
- setNotice(null);
968
- setError(null);
969
- const result = await props.client.mutateRuntimeMemory(request);
970
- setNotice(result.maintenance === undefined
971
- ? t(`runtime.result.${request.action}`, { target: t(`runtime.target.${request.target}`), count: result.entryCount })
972
- : result.maintenance.kind === 'local-compaction'
973
- ? t('runtime.result.localCompaction', { target: t(`runtime.target.${request.target}`), count: result.entryCount })
974
- : t('runtime.result.maintenance', { target: t(`runtime.target.${request.target}`), count: result.entryCount, spaces: result.maintenance.memoryBodyIds.join(', ') || '—' }));
975
- await load();
976
- props.onMutate();
977
- };
978
- const add = async (event) => {
979
- event.preventDefault();
980
- if (content.trim() === '')
981
- return;
982
- setSaving(true);
983
- try {
984
- await mutate({ action: 'add', target, content, importance });
985
- setContent('');
986
- if (appearance.surface === 'sidebar')
987
- setAdding(false);
988
- }
989
- catch (reason) {
990
- setError(message(reason));
991
- }
992
- finally {
993
- setSaving(false);
994
- }
995
- };
996
- const beginEdit = (entry) => {
997
- setEditing(entryKey(entry));
998
- setEditContent(entry.content);
999
- setEditImportance(entry.importance);
1000
- setRemoving(null);
1001
- };
1002
- const replace = async (entry) => {
1003
- if (editContent.trim() === '')
1004
- return;
1005
- setSaving(true);
1006
- try {
1007
- await mutate({ action: 'replace', target: entry.target, old_text: entry.content, content: editContent, importance: editImportance });
1008
- setEditing(null);
1009
- }
1010
- catch (reason) {
1011
- setError(message(reason));
1012
- }
1013
- finally {
1014
- setSaving(false);
1015
- }
1016
- };
1017
- const remove = async (entry) => {
1018
- setSaving(true);
1019
- try {
1020
- await mutate({ action: 'remove', target: entry.target, old_text: entry.content });
1021
- setRemoving(null);
1022
- }
1023
- catch (reason) {
1024
- setError(message(reason));
1025
- }
1026
- finally {
1027
- setSaving(false);
1028
- }
1029
- };
1030
- const runtimeEditActionClass = appearance.surface === 'sidebar'
1031
- ? appearanceClass(css.ghostButton, appearanceClass(appearance.classes.itemActionButton, appearance.classes.itemEditAction))
1032
- : css.ghostButton;
1033
- const runtimeRemoveActionClass = appearance.surface === 'sidebar'
1034
- ? appearanceClass(css.dangerButton, appearanceClass(appearance.classes.itemActionButton, appearance.classes.itemDangerAction))
1035
- : css.dangerButton;
1036
- const runtimeEntry = (entry, showTarget = false) => {
1037
- const key = entryKey(entry);
1038
- const isEditing = editing === key;
1039
- const isInlineEditing = appearance.surface === 'buildin' && isEditing;
1040
- const isRemoving = removing === key;
1041
- const isInlineRemoving = appearance.surface === 'buildin' && isRemoving;
1042
- return _jsxs("article", { className: css.runtimeEntry, "data-importance": entry.importance, "data-target": entry.target, children: [_jsxs("div", { className: css.runtimeEntryMeta, children: [showTarget ? _jsxs("div", { className: css.runtimeEntryBadges, children: [_jsx("span", { className: css.runtimeEntryTarget, children: entry.target === 'user' ? 'USER.md' : 'MEMORY.md' }), _jsx("span", { children: t(`runtime.importance.${entry.importance}`) })] }) : _jsx("span", { children: t(`runtime.importance.${entry.importance}`) }), _jsx("time", { dateTime: entry.updated_at, children: new Date(entry.updated_at).toLocaleString(locale) })] }), isInlineEditing ? _jsx("textarea", { "aria-label": t('runtime.editContent'), value: editContent, onChange: event => setEditContent(event.target.value), rows: 4 }) : _jsx("p", { children: entry.content }), isInlineEditing && _jsxs("select", { "aria-label": t('runtime.importance'), value: editImportance, onChange: event => setEditImportance(event.target.value), children: [_jsx("option", { value: "critical", children: t('runtime.importance.critical') }), _jsx("option", { value: "normal", children: t('runtime.importance.normal') }), _jsx("option", { value: "low", children: t('runtime.importance.low') })] }), _jsx("footer", { children: isInlineRemoving ? _jsxs(_Fragment, { children: [_jsx("span", { children: t('runtime.removeConfirm') }), _jsx("button", { type: "button", className: css.dangerSolidButton, disabled: saving, onClick: () => void remove(entry), children: t('runtime.removeAction') }), _jsx("button", { type: "button", className: css.ghostButton, onClick: () => setRemoving(null), children: t('common.cancel') })] }) : isInlineEditing ? _jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: css.primaryButton, disabled: saving || editContent.trim() === '', onClick: () => void replace(entry), children: t('runtime.saveEdit') }), _jsx("button", { type: "button", className: css.ghostButton, onClick: () => setEditing(null), children: t('common.cancel') })] }) : props.writeEnabled ? _jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: runtimeEditActionClass, disabled: saving && isRemoving, onClick: () => beginEdit(entry), children: t('runtime.editAction') }), _jsx("button", { type: "button", className: runtimeRemoveActionClass, disabled: saving && isRemoving, onClick: () => { setRemoving(key); setEditing(null); }, children: t('runtime.removeAction') })] }) : null })] }, key);
1043
- };
1044
- const targetPanel = (value) => {
1045
- const view = snapshot?.targets[value];
1046
- const entries = snapshot?.entries.filter(entry => entry.target === value) ?? [];
1047
- const percentage = view === undefined || view.limit === 0 ? 0 : Math.min(100, Math.round(view.used / view.limit * 100));
1048
- return (_jsxs("section", { className: css.runtimeTarget, "aria-label": t(`runtime.target.${value}`), children: [_jsxs("header", { className: css.runtimeTargetHeader, children: [_jsxs("div", { children: [_jsx("span", { children: value === 'user' ? 'USER.md' : 'MEMORY.md' }), _jsx("h3", { children: t(`runtime.target.${value}`) })] }), _jsx("strong", { children: view?.entryCount ?? 0 })] }), _jsxs("div", { className: css.capacityLine, children: [_jsx("div", { children: _jsx("i", { style: { width: `${percentage}%` } }) }), _jsx("span", { children: view === undefined ? '—' : `${humanBytes(view.used)} / ${humanBytes(view.limit)}` })] }), _jsx("p", { className: css.runtimeTargetDescription, children: t(`runtime.target.${value}.description`) }), _jsxs("div", { className: css.runtimeEntries, children: [entries.map(entry => runtimeEntry(entry)), !loading && entries.length === 0 && _jsxs("div", { className: css.runtimeEmpty, children: [_jsx("span", { children: "\u25CB" }), _jsx("p", { children: t('runtime.empty') })] })] })] }));
1049
- };
1050
- const targetSummary = (value) => {
1051
- const view = snapshot?.targets[value];
1052
- const percentage = view === undefined || view.limit === 0 ? 0 : Math.min(100, Math.round(view.used / view.limit * 100));
1053
- return _jsxs("section", { className: css.runtimeSummaryCard, "aria-label": t(`runtime.target.${value}`), children: [_jsxs("header", { className: css.runtimeTargetHeader, children: [_jsxs("div", { children: [_jsx("span", { children: value === 'user' ? 'USER.md' : 'MEMORY.md' }), _jsx("h3", { children: t(`runtime.target.${value}`) })] }), _jsx("strong", { children: view?.entryCount ?? 0 })] }), _jsxs("div", { className: css.capacityLine, children: [_jsx("div", { children: _jsx("i", { style: { width: `${percentage}%` } }) }), _jsx("span", { children: view === undefined ? '—' : `${humanBytes(view.used)} / ${humanBytes(view.limit)}` })] }), _jsx("p", { className: css.runtimeTargetDescription, children: t(`runtime.target.${value}.description`) })] });
1054
- };
1055
- const normalizedQuery = filterQuery.trim().toLocaleLowerCase();
1056
- const filteredEntries = (snapshot?.entries ?? []).filter(entry => (filterTarget === 'all' || entry.target === filterTarget) && (normalizedQuery === '' || entry.content.toLocaleLowerCase().includes(normalizedQuery)));
1057
- const visibleEntries = filteredEntries.slice(0, visibleLimit);
1058
- const closeComposer = () => {
1059
- setContent('');
1060
- setAdding(false);
1061
- };
1062
- const composer = _jsxs("form", { className: css.runtimeComposer, onSubmit: event => void add(event), children: [_jsxs("div", { className: css.runtimeComposerHeading, children: [_jsxs("div", { children: [_jsx("h3", { children: t('runtime.addTitle') }), _jsx("p", { children: t('runtime.addDescription') })] }), _jsx("span", { children: t('runtime.hotContext') })] }), _jsx("textarea", { "aria-label": t('runtime.content'), value: content, onChange: event => setContent(event.target.value), rows: 3, placeholder: t('runtime.placeholder') }), _jsxs("div", { className: css.runtimeComposerActions, children: [_jsxs("label", { children: [t('runtime.target'), _jsxs("select", { value: target, onChange: event => setTarget(event.target.value), children: [_jsx("option", { value: "memory", children: t('runtime.target.memory') }), _jsx("option", { value: "user", children: t('runtime.target.user') })] })] }), _jsxs("label", { children: [t('runtime.importance'), _jsxs("select", { value: importance, onChange: event => setImportance(event.target.value), children: [_jsx("option", { value: "critical", children: t('runtime.importance.critical') }), _jsx("option", { value: "normal", children: t('runtime.importance.normal') }), _jsx("option", { value: "low", children: t('runtime.importance.low') })] })] }), appearance.surface === 'sidebar' && _jsx("button", { type: "button", className: css.ghostButton, disabled: saving, onClick: closeComposer, children: t('common.cancel') }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: saving || content.trim() === '', children: saving ? t('runtime.saving') : t('runtime.addAction') })] })] });
1063
- const editingEntry = editing === null ? undefined : snapshot?.entries.find(entry => entryKey(entry) === editing);
1064
- const removingEntry = removing === null ? undefined : snapshot?.entries.find(entry => entryKey(entry) === removing);
1065
- const editForm = editingEntry === undefined ? null : _jsxs("form", { className: css.bodyEdit, onSubmit: event => { event.preventDefault(); void replace(editingEntry); }, children: [_jsxs("label", { children: [t('runtime.editContent'), _jsx("textarea", { "aria-label": t('runtime.editContent'), value: editContent, onChange: event => setEditContent(event.target.value), rows: 7 })] }), _jsxs("label", { children: [t('runtime.importance'), _jsxs("select", { "aria-label": t('runtime.importance'), value: editImportance, onChange: event => setEditImportance(event.target.value), children: [_jsx("option", { value: "critical", children: t('runtime.importance.critical') }), _jsx("option", { value: "normal", children: t('runtime.importance.normal') }), _jsx("option", { value: "low", children: t('runtime.importance.low') })] })] }), _jsxs("div", { className: css.bodyEditActions, children: [_jsx("button", { type: "button", className: css.ghostButton, disabled: saving, onClick: () => setEditing(null), children: t('common.cancel') }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: saving || editContent.trim() === '', children: t('runtime.saveEdit') })] })] });
1066
- return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('runtime.title'), description: t('runtime.description'), meta: snapshot === null ? t('common.loading') : t('runtime.total', { count: snapshot.entries.length }), action: _jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: css.secondaryButton, disabled: loading, onClick: () => void load(), children: t('runtime.refresh') }), appearance.surface === 'sidebar' && props.writeEnabled && _jsx("button", { type: "button", className: css.primaryButton, onClick: () => setAdding(true), children: t('runtime.addButton') })] }) }), error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), notice !== null && _jsx("div", { className: css.runtimeNotice, role: "status", children: notice }), props.writeEnabled && appearance.surface === 'buildin' && composer, !props.writeEnabled && _jsx("div", { className: css.runtimeReadOnly, children: t('runtime.readOnly') }), appearance.surface === 'buildin' ? _jsxs("div", { className: css.runtimeGrid, children: [targetPanel('user'), targetPanel('memory')] }) : _jsxs(_Fragment, { children: [_jsxs("div", { className: css.runtimeSummaryGrid, children: [targetSummary('user'), targetSummary('memory')] }), _jsxs("section", { className: css.runtimeBrowser, "aria-label": t('runtime.entriesAria'), children: [_jsxs("div", { className: css.runtimeBrowserToolbar, children: [_jsxs("div", { className: css.runtimeScopeFilter, role: "group", "aria-label": t('runtime.scopeAria'), children: [_jsxs("button", { type: "button", "data-active": filterTarget === 'all' || undefined, onClick: () => setFilterTarget('all'), children: [t('runtime.scopeAll'), " ", _jsx("b", { children: snapshot?.entries.length ?? 0 })] }), _jsxs("button", { type: "button", "data-active": filterTarget === 'user' || undefined, onClick: () => setFilterTarget('user'), children: [t('runtime.target.user'), " ", _jsx("b", { children: snapshot?.targets.user.entryCount ?? 0 })] }), _jsxs("button", { type: "button", "data-active": filterTarget === 'memory' || undefined, onClick: () => setFilterTarget('memory'), children: [t('runtime.target.memory'), " ", _jsx("b", { children: snapshot?.targets.memory.entryCount ?? 0 })] })] }), _jsxs("div", { className: css.runtimeFilterQuery, children: [_jsx("span", { "aria-hidden": "true", children: "\u2315" }), _jsx("input", { "aria-label": t('runtime.filterAria'), value: filterQuery, onChange: event => setFilterQuery(event.target.value), placeholder: t('runtime.filterPlaceholder') })] })] }), _jsxs("div", { className: css.runtimeUnifiedList, children: [visibleEntries.map(entry => runtimeEntry(entry, true)), !loading && filteredEntries.length === 0 && _jsxs("div", { className: css.runtimeEmpty, children: [_jsx("span", { children: "\u25CB" }), _jsx("p", { children: t('runtime.noMatch') })] })] }), !loading && _jsx(ProgressiveFooter, { visible: visibleEntries.length, total: filteredEntries.length, pageSize: pageSize, onMore: () => setVisibleLimit(value => value + pageSize) })] })] }), _jsx("p", { className: css.runtimeFootnote, children: t('runtime.footnote') }), appearance.surface === 'sidebar' && adding && _jsx(SidebarModal, { title: t('runtime.addTitle'), description: t('runtime.addDescription'), busy: saving, onClose: closeComposer, children: composer }), appearance.surface === 'sidebar' && editingEntry !== undefined && _jsx(SidebarModal, { title: t('runtime.editContent'), description: t(`runtime.target.${editingEntry.target}`), busy: saving, onClose: () => setEditing(null), children: editForm }), appearance.surface === 'sidebar' && removingEntry !== undefined && _jsx(SidebarModal, { title: t('runtime.removeTitle'), description: t(`runtime.target.${removingEntry.target}`), busy: saving, onClose: () => setRemoving(null), children: _jsxs("div", { className: css.bodyDeleteConfirm, children: [_jsx("p", { children: t('runtime.removeWarning') }), _jsxs("div", { className: css.bodyDeleteSummary, children: [_jsx("p", { className: css.bodyDeleteContent, children: removingEntry.content }), _jsx("span", { children: t(`runtime.importance.${removingEntry.importance}`) })] }), _jsxs("div", { className: css.bodyEditActions, children: [_jsx("button", { type: "button", "data-autofocus": true, className: css.ghostButton, disabled: saving, onClick: () => setRemoving(null), children: t('common.cancel') }), _jsx("button", { type: "button", className: css.dangerSolidButton, disabled: saving, onClick: () => void remove(removingEntry), children: t('runtime.removeAction') })] })] }) })] }));
1067
- }
1068
- function RememberPage(props) {
1069
- const t = useT();
1070
- const [content, setContent] = useState(props.seed);
1071
- const [category, setCategory] = useState('general');
1072
- const [importance, setImportance] = useState(3);
1073
- const [tags, setTags] = useState('');
1074
- const [entities, setEntities] = useState('');
1075
- const [memoryBodyId, setMemoryBodyId] = useState('');
1076
- const [supervising, setSupervising] = useState(false);
1077
- const [saving, setSaving] = useState(false);
1078
- const [result, setResult] = useState(null);
1079
- useEffect(() => { if (props.seed !== '')
1080
- setContent(props.seed); }, [props.seed]);
1081
- useEffect(() => {
1082
- if (memoryBodyId === '' && props.memoryBodies.length > 0)
1083
- setMemoryBodyId((props.memoryBodies.find(body => body.active) ?? props.memoryBodies[0]).id);
1084
- }, [memoryBodyId, props.memoryBodies]);
1085
- const supervise = async (event) => {
1086
- event.preventDefault();
1087
- if (content.trim() === '' || props.sessionId === undefined)
1088
- return;
1089
- setSupervising(true);
1090
- setResult(null);
1091
- try {
1092
- const response = await props.client.supervise(content);
1093
- setResult(`${t(response.action === 'skipped' ? 'remember.skipped' : 'remember.completed')}${response.memoryBodyIds.length === 0 ? '' : ` · ${response.memoryBodyIds.join(', ')}`}${response.summary === '' ? '' : ` · ${response.summary}`}`);
1094
- props.onMutate();
1095
- if (response.action !== 'skipped') {
1096
- setContent('');
1097
- props.onComplete?.();
1098
- }
1099
- }
1100
- catch (reason) {
1101
- setResult(t('remember.dispatchFailed', { error: message(reason) }));
1102
- }
1103
- finally {
1104
- setSupervising(false);
1105
- }
1106
- };
1107
- const manualSave = async (event) => {
1108
- event.preventDefault();
1109
- if (content.trim() === '')
1110
- return;
1111
- setSaving(true);
1112
- setResult(null);
1113
- try {
1114
- const response = await props.client.remember({ content, category, importance, tags: tags.split(',').map(value => value.trim()).filter(Boolean), entities: entities.split(',').map(value => value.trim()).filter(Boolean), source: 'user', ...(memoryBodyId === '' ? {} : { memoryBodyId }) });
1115
- const action = typeof response.action === 'string' ? response.action : 'saved';
1116
- const summary = typeof response.summary === 'string' ? response.summary : '';
1117
- setResult(action === 'skipped' ? `${t('remember.skipped')}${summary === '' ? '' : ` · ${summary}`}` : `${t('remember.processed', { action })}${summary === '' ? '' : ` · ${summary}`}`);
1118
- if (action !== 'skipped') {
1119
- setContent('');
1120
- setTags('');
1121
- setEntities('');
1122
- props.onMutate();
1123
- props.onComplete?.();
1124
- }
1125
- }
1126
- catch (reason) {
1127
- setResult(t('remember.saveFailed', { error: message(reason) }));
1128
- }
1129
- finally {
1130
- setSaving(false);
1131
- }
1132
- };
1133
- const composer = _jsxs("section", { className: css.supervisedComposer, children: [_jsxs("form", { className: css.supervisedForm, onSubmit: event => void supervise(event), children: [_jsxs("div", { className: css.supervisedHeading, children: [_jsx("div", { children: _jsx("h3", { children: t('remember.delegateTitle') }) }), _jsx("span", { className: props.sessionId === undefined ? css.sessionMissing : css.sessionReady, children: props.sessionId === undefined ? t('remember.noSession') : t('remember.ready') })] }), _jsxs("label", { className: css.fieldWide, children: [t('remember.candidate'), _jsx("textarea", { "aria-label": t('remember.candidateAria'), value: content, onChange: event => setContent(event.target.value), maxLength: 8000, rows: 8, placeholder: t('remember.placeholder') })] }), props.sessionId === undefined && _jsx("p", { className: css.sessionHint, children: t('remember.sessionHint') }), _jsxs("div", { className: css.formActions, children: [props.onClose !== undefined && _jsx("button", { type: "button", className: css.ghostButton, disabled: supervising || saving, onClick: props.onClose, children: t('common.cancel') }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: supervising || content.trim() === '' || props.sessionId === undefined, children: supervising ? t('remember.processing') : t('remember.action') }), result !== null && _jsx("span", { role: "status", children: result })] })] }), _jsxs("details", { className: css.advancedWrite, children: [_jsxs("summary", { children: [_jsxs("span", { children: [_jsx("strong", { children: t('remember.advanced') }), _jsx("small", { children: t('remember.advancedHint') })] }), _jsx("span", { children: t('remember.expand') })] }), _jsxs("form", { className: css.manualForm, onSubmit: event => void manualSave(event), children: [_jsxs("div", { className: css.formGrid, children: [_jsxs("label", { className: css.fieldWide, children: [t('remember.target'), _jsx("select", { "aria-label": t('remember.target'), value: memoryBodyId, onChange: event => setMemoryBodyId(event.target.value), children: props.memoryBodies.map(body => _jsxs("option", { value: body.id, children: [body.name, " \u00B7 ", body.id, body.active ? ` · ${t('common.active')}` : ''] }, body.id)) })] }), _jsxs("label", { children: [t('common.category'), _jsx("select", { value: category, onChange: event => setCategory(event.target.value), children: CATEGORIES.map(value => _jsx("option", { value: value, children: categoryLabel(t, value) }, value)) })] }), _jsxs("label", { children: [t('common.importanceLabel'), _jsx("select", { value: importance, onChange: event => setImportance(Number(event.target.value)), children: [1, 2, 3, 4, 5].map(value => _jsxs("option", { value: value, children: [value, " / 5"] }, value)) })] }), _jsxs("label", { className: css.fieldWide, children: [t('remember.entities'), _jsx("input", { value: entities, onChange: event => setEntities(event.target.value), placeholder: "SQLite, DSH" })] }), _jsxs("label", { className: css.fieldWide, children: [t('remember.tags'), _jsx("input", { value: tags, onChange: event => setTags(event.target.value), placeholder: "architecture, local-first" })] })] }), _jsxs("div", { className: css.manualActions, children: [_jsx("p", { children: t('remember.advancedText') }), _jsx("button", { type: "submit", className: css.secondaryButton, disabled: saving || content.trim() === '' || props.sessionId === undefined || memoryBodyId === '', children: saving ? t('remember.saving') : t('remember.advancedAction') })] })] })] })] });
1134
- if (props.onClose !== undefined) {
1135
- return _jsx(SidebarModal, { title: t('remember.title'), description: t('remember.description'), busy: supervising || saving, onClose: props.onClose, children: props.writeEnabled ? composer : _jsx(EmptyState, { glyph: "\u2298", title: t('remember.readOnlyTitle'), children: t('remember.readOnlyText') }) });
1136
- }
1137
- return _jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('remember.title'), description: t('remember.description'), meta: props.writeEnabled ? t('remember.worker') : t('common.readOnly') }), !props.writeEnabled ? _jsx(EmptyState, { glyph: "\u2298", title: t('remember.readOnlyTitle'), children: t('remember.readOnlyText') }) : _jsxs("div", { className: css.writebackLayout, children: [_jsxs("aside", { className: css.writeGuide, children: [_jsx("h3", { children: t('remember.flowTitle') }), _jsxs("ol", { children: [_jsxs("li", { children: [_jsx("strong", { children: t('remember.routeTitle') }), _jsx("span", { children: t('remember.routeText') })] }), _jsxs("li", { children: [_jsx("strong", { children: t('remember.dedupeTitle') }), _jsx("span", { children: t('remember.dedupeText') })] }), _jsxs("li", { children: [_jsx("strong", { children: t('remember.writeTitle') }), _jsx("span", { children: t('remember.writeText') })] })] }), _jsx("p", { children: t('remember.flowText') })] }), composer] })] });
1138
- }
1139
- function ListPage(props) {
1140
- const t = useT();
1141
- const appearance = useMnemonViewAppearance();
1142
- const pageSize = appearance.surface === 'sidebar' ? 12 : 48;
1143
- const [query, setQuery] = useState('');
1144
- const [category, setCategory] = useState('');
1145
- const [view, setView] = useState(null);
1146
- const [loading, setLoading] = useState(true);
1147
- const [error, setError] = useState(null);
1148
- const [visibleLimit, setVisibleLimit] = useState(pageSize);
1149
- const load = useCallback(async () => {
1150
- setLoading(true);
1151
- setError(null);
1152
- try {
1153
- setView(await props.client.list({ ...(query.trim() === '' ? {} : { query }), ...(category === '' ? {} : { category }), limit: 1000 }));
1154
- }
1155
- catch (reason) {
1156
- setError(message(reason));
1157
- }
1158
- finally {
1159
- setLoading(false);
1160
- }
1161
- }, [category, props.client, query]);
1162
- useEffect(() => { setVisibleLimit(pageSize); void load(); }, [pageSize, props.revision]);
1163
- const submit = (event) => { event.preventDefault(); setVisibleLimit(pageSize); void load(); };
1164
- const forget = async (insight) => { await props.onForget(insight); setView(current => current === null ? current : { ...current, total: Math.max(0, current.total - 1), items: current.items.filter(item => insightKey(item) !== insightKey(insight)) }); };
1165
- const visibleItems = view?.items.slice(0, visibleLimit) ?? [];
1166
- return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('content.title'), description: t('content.description'), meta: t('content.count', { count: view?.total ?? '—' }) }), _jsxs("form", { className: css.listToolbar, onSubmit: submit, children: [_jsx("input", { "aria-label": t('content.filterAria'), value: query, onChange: event => setQuery(event.target.value), placeholder: t('content.filterPlaceholder') }), _jsxs("select", { "aria-label": t('content.categoryAria'), value: category, onChange: event => setCategory(event.target.value), children: [_jsx("option", { value: "", children: t('common.allCategories') }), CATEGORIES.map(value => _jsx("option", { value: value, children: categoryLabel(t, value) }, value))] }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: loading, children: loading ? t('common.loading') : t('content.apply') })] }), _jsx("div", { className: css.listNotice, children: t('content.notice') }), error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), !loading && view?.items.length === 0 && _jsx(EmptyState, { glyph: "\u2261", title: t('content.emptyTitle'), children: t('content.emptyText') }), _jsx("div", { className: css.memoryList, children: visibleItems.map(insight => _jsx(InsightCard, { insight: insight, writeEnabled: props.writeEnabled, onForget: forget, onClone: props.onClone, onRelated: () => props.onExplore(insight.content) }, insightKey(insight))) }), view !== null && _jsx(ProgressiveFooter, { visible: visibleItems.length, total: view.items.length, pageSize: pageSize, onMore: () => setVisibleLimit(value => value + pageSize) })] }));
1167
- }
1168
- function DocumentsPage(props) {
1169
- const t = useT();
1170
- const locale = useLocale();
1171
- const appearance = useMnemonViewAppearance();
1172
- const pageSize = appearance.surface === 'sidebar' ? 8 : Number.MAX_SAFE_INTEGER;
1173
- const readerRef = useRef(null);
1174
- const [snapshot, setSnapshot] = useState(null);
1175
- const [items, setItems] = useState([]);
1176
- const [visibleLimit, setVisibleLimit] = useState(pageSize);
1177
- const [selectedId, setSelectedId] = useState(null);
1178
- const [selected, setSelected] = useState(null);
1179
- const [status, setStatus] = useState('active');
1180
- const [query, setQuery] = useState('');
1181
- const [loading, setLoading] = useState(true);
1182
- const [saving, setSaving] = useState(false);
1183
- const [error, setError] = useState(null);
1184
- const [notice, setNotice] = useState(null);
1185
- const [composing, setComposing] = useState(false);
1186
- const [editing, setEditing] = useState(false);
1187
- const [confirmArchive, setConfirmArchive] = useState(false);
1188
- const [title, setTitle] = useState('');
1189
- const [description, setDescription] = useState('');
1190
- const [content, setContent] = useState('');
1191
- const [sources, setSources] = useState('');
1192
- const display = useCallback(async (nextQuery, nextStatus) => {
1193
- setLoading(true);
1194
- setError(null);
1195
- setVisibleLimit(pageSize);
1196
- try {
1197
- const current = await props.client.documents();
1198
- const records = nextQuery.trim() === ''
1199
- ? current.documents
1200
- : (await props.client.searchDocuments(nextQuery, nextStatus === 'archived')).results;
1201
- const filtered = records.filter(record => record.status === nextStatus);
1202
- setSnapshot(current);
1203
- setItems(filtered);
1204
- setSelectedId(previous => previous !== null && filtered.some(record => record.id === previous) ? previous : filtered[0]?.id ?? null);
1205
- }
1206
- catch (reason) {
1207
- setError(message(reason));
1208
- setSnapshot(null);
1209
- setItems([]);
1210
- setSelectedId(null);
1211
- }
1212
- finally {
1213
- setLoading(false);
1214
- }
1215
- }, [pageSize, props.client]);
1216
- useEffect(() => { void display(query, status); }, [display, props.revision, status]);
1217
- useEffect(() => {
1218
- setSelected(null);
1219
- if (selectedId === null)
1220
- return;
1221
- let active = true;
1222
- void props.client.document(selectedId).then(value => { if (active)
1223
- setSelected(value); }).catch(reason => { if (active)
1224
- setError(message(reason)); });
1225
- return () => { active = false; };
1226
- }, [props.client, selectedId, props.revision]);
1227
- useLayoutEffect(() => {
1228
- if (appearance.surface === 'sidebar' && readerRef.current !== null)
1229
- readerRef.current.scrollTop = 0;
1230
- }, [appearance.surface, selectedId]);
1231
- useEffect(() => {
1232
- if (appearance.surface !== 'sidebar' || selectedId === null)
1233
- return;
1234
- const index = items.findIndex(item => item.id === selectedId);
1235
- if (index >= visibleLimit)
1236
- setVisibleLimit(Math.ceil((index + 1) / pageSize) * pageSize);
1237
- }, [appearance.surface, items, pageSize, selectedId, visibleLimit]);
1238
- const resetComposer = () => { setTitle(''); setDescription(''); setContent(''); setSources(''); setComposing(false); };
1239
- const startComposer = () => { setTitle(''); setDescription(''); setContent(''); setSources(''); setEditing(false); setComposing(true); };
1240
- const sourcePaths = (value) => value.split(/\r?\n|,/gu).map(path => path.trim()).filter(Boolean);
1241
- const create = async (event) => {
1242
- event.preventDefault();
1243
- setSaving(true);
1244
- setError(null);
1245
- setNotice(null);
1246
- try {
1247
- const result = await props.client.mutateDocument({ action: 'create', title, description, content, sourcePaths: sourcePaths(sources) });
1248
- setNotice(result.maintenance === undefined ? t('documents.created') : t('documents.createdAfterArchive', { count: result.maintenance.archivedDocumentIds.length }));
1249
- setStatus('active');
1250
- setQuery('');
1251
- resetComposer();
1252
- props.onMutate();
1253
- await display('', 'active');
1254
- setSelectedId(result.document.id);
1255
- }
1256
- catch (reason) {
1257
- setError(message(reason));
1258
- }
1259
- finally {
1260
- setSaving(false);
1261
- }
1262
- };
1263
- const beginEdit = () => {
1264
- if (selected === null)
1265
- return;
1266
- setTitle(selected.title);
1267
- setDescription(selected.description);
1268
- setContent(selected.content);
1269
- setSources(selected.sourcePaths.join('\n'));
1270
- setEditing(true);
1271
- setComposing(false);
1272
- setConfirmArchive(false);
1273
- };
1274
- const update = async (event) => {
1275
- event.preventDefault();
1276
- if (selected === null)
1277
- return;
1278
- setSaving(true);
1279
- setError(null);
1280
- setNotice(null);
1281
- try {
1282
- const result = await props.client.mutateDocument({ action: 'update', id: selected.id, title, description, content, sourcePaths: sourcePaths(sources) });
1283
- setNotice(result.maintenance === undefined ? t('documents.updated') : t('documents.updatedAfterArchive', { count: result.maintenance.archivedDocumentIds.length }));
1284
- setEditing(false);
1285
- props.onMutate();
1286
- await display(query, status);
1287
- setSelectedId(result.document.id);
1288
- }
1289
- catch (reason) {
1290
- setError(message(reason));
1291
- }
1292
- finally {
1293
- setSaving(false);
1294
- }
1295
- };
1296
- const archive = async () => {
1297
- if (selected === null)
1298
- return;
1299
- setSaving(true);
1300
- setError(null);
1301
- setNotice(null);
1302
- try {
1303
- const result = await props.client.archiveDocument(selected.id);
1304
- setNotice(t('documents.archived', { spaces: result.maintenance?.memoryBodyIds.join(', ') || '—' }));
1305
- setConfirmArchive(false);
1306
- setStatus('archived');
1307
- setQuery('');
1308
- props.onMutate();
1309
- await display('', 'archived');
1310
- setSelectedId(result.document.id);
1311
- }
1312
- catch (reason) {
1313
- setError(message(reason));
1314
- }
1315
- finally {
1316
- setSaving(false);
1317
- }
1318
- };
1319
- const usage = snapshot === null ? 0 : Math.min(100, snapshot.activeBytes / snapshot.limitBytes * 100);
1320
- const activeCount = snapshot?.activeCount ?? 0;
1321
- const archivedCount = snapshot?.archivedCount ?? 0;
1322
- const composer = _jsxs("form", { className: css.documentEditor, onSubmit: event => void create(event), children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("h3", { children: t('documents.newTitle') }), _jsx("p", { children: t('documents.editorHint') })] }), _jsx("span", { children: t('documents.managedCopy') })] }), _jsxs("div", { className: css.documentEditorMeta, children: [_jsxs("label", { children: [t('documents.name'), _jsx("input", { value: title, onChange: event => setTitle(event.target.value), required: true })] }), _jsxs("label", { children: [t('documents.routing'), _jsx("input", { value: description, onChange: event => setDescription(event.target.value) })] })] }), _jsxs("label", { children: [t('documents.sources'), _jsx("input", { value: sources, onChange: event => setSources(event.target.value), placeholder: t('documents.sourcesPlaceholder') })] }), _jsxs("label", { children: [t('documents.markdown'), _jsx("textarea", { value: content, onChange: event => setContent(event.target.value), rows: 10, required: true })] }), _jsxs("footer", { children: [_jsx("button", { type: "button", className: css.ghostButton, disabled: saving, onClick: resetComposer, children: t('common.cancel') }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: saving || title.trim() === '' || content.trim() === '', children: saving ? t('documents.saving') : t('documents.create') })] })] });
1323
- const editComposer = selected === null ? null : _jsxs("form", { className: css.documentEditor, onSubmit: event => void update(event), children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("h3", { children: t('documents.editTitle') }), _jsx("p", { children: t('documents.editorHint') })] }), _jsx("code", { children: selected.id })] }), _jsxs("div", { className: css.documentEditorMeta, children: [_jsxs("label", { children: [t('documents.name'), _jsx("input", { value: title, onChange: event => setTitle(event.target.value), required: true })] }), _jsxs("label", { children: [t('documents.routing'), _jsx("input", { value: description, onChange: event => setDescription(event.target.value) })] })] }), _jsxs("label", { children: [t('documents.sources'), _jsx("input", { value: sources, onChange: event => setSources(event.target.value) })] }), _jsxs("label", { children: [t('documents.markdown'), _jsx("textarea", { value: content, onChange: event => setContent(event.target.value), rows: 18, required: true })] }), _jsxs("footer", { children: [_jsx("button", { type: "button", className: css.ghostButton, disabled: saving, onClick: () => setEditing(false), children: t('common.cancel') }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: saving, children: saving ? t('documents.saving') : t('documents.save') })] })] });
1324
- const documentEditActionClass = appearance.surface === 'sidebar'
1325
- ? appearanceClass(css.ghostButton, appearanceClass(appearance.classes.itemActionButton, appearance.classes.itemEditAction))
1326
- : css.secondaryButton;
1327
- const documentArchiveActionClass = appearance.surface === 'sidebar'
1328
- ? appearanceClass(css.dangerButton, appearanceClass(appearance.classes.itemActionButton, appearance.classes.itemDangerAction))
1329
- : css.dangerButton;
1330
- const visibleItems = items.slice(0, visibleLimit);
1331
- return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('documents.title'), description: t('documents.description'), meta: snapshot === null ? t('common.loading') : t('documents.capacity', { used: humanBytes(snapshot.activeBytes), limit: humanBytes(snapshot.limitBytes) }), action: _jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: css.secondaryButton, disabled: loading, onClick: () => void display(query, status), children: t('documents.refresh') }), appearance.surface === 'sidebar' && props.writeEnabled && props.sessionId !== undefined && _jsx("button", { type: "button", className: css.primaryButton, onClick: startComposer, children: t('documents.new') })] }) }), error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), notice !== null && _jsx("div", { className: css.runtimeNotice, role: "status", children: notice }), _jsxs("section", { className: css.documentSummary, "aria-label": t('documents.summary'), children: [_jsxs("article", { children: [_jsx("span", { children: t('documents.active') }), _jsx("strong", { children: activeCount }), _jsx("small", { children: t('documents.activeHint') })] }), _jsxs("article", { children: [_jsx("span", { children: t('documents.archivedCount') }), _jsx("strong", { children: archivedCount }), _jsx("small", { children: t('documents.archivedHint') })] }), _jsxs("article", { className: css.documentCapacity, children: [_jsx("span", { children: t('documents.activeCapacity') }), _jsx("strong", { children: snapshot === null ? '—' : `${usage.toFixed(1)}%` }), _jsx("div", { children: _jsx("i", { style: { width: `${usage}%` } }) }), _jsx("small", { children: t('documents.capacityHint') })] })] }), _jsxs("section", { className: css.documentToolbar, children: [_jsxs("form", { onSubmit: event => { event.preventDefault(); void display(query, status); }, children: [_jsx("span", { "aria-hidden": "true", children: "\u2315" }), _jsx("input", { "aria-label": t('documents.searchAria'), value: query, onChange: event => setQuery(event.target.value), placeholder: t('documents.searchPlaceholder') }), _jsx("button", { type: "submit", className: css.secondaryButton, children: t('documents.search') })] }), _jsxs("div", { role: "group", "aria-label": t('documents.scope'), children: [_jsxs("button", { type: "button", "data-active": status === 'active' || undefined, onClick: () => setStatus('active'), children: [t('documents.active'), " ", _jsx("b", { children: activeCount })] }), _jsxs("button", { type: "button", "data-active": status === 'archived' || undefined, onClick: () => setStatus('archived'), children: [t('documents.archivedCount'), " ", _jsx("b", { children: archivedCount })] })] }), appearance.surface === 'buildin' && props.writeEnabled && props.sessionId !== undefined && _jsx("button", { type: "button", className: css.primaryButton, onClick: () => { if (composing)
1332
- resetComposer();
1333
- else
1334
- startComposer(); }, children: composing ? t('common.cancel') : t('documents.new') })] }), composing && appearance.surface === 'buildin' && composer, _jsxs("div", { className: css.documentWorkspace, children: [_jsxs("aside", { className: css.documentList, "aria-label": t('documents.list'), children: [_jsxs("header", { children: [_jsx("span", { children: status === 'active' ? t('documents.activeList') : t('documents.archiveList') }), _jsx("code", { children: items.length })] }), visibleItems.map(document => _jsxs("button", { type: "button", "data-selected": selectedId === document.id || undefined, onClick: () => { setSelected(null); setSelectedId(document.id); setEditing(false); setConfirmArchive(false); }, children: [_jsxs("div", { children: [_jsx("strong", { children: document.title }), _jsx("time", { dateTime: document.updatedAt, children: new Date(document.updatedAt).toLocaleDateString(locale) })] }), _jsx("p", { children: document.description || document.excerpt || t('documents.noDescription') }), _jsxs("footer", { children: [_jsx("span", { children: humanBytes(document.sizeBytes) }), _jsx("code", { children: document.id.slice(0, 8) }), document.healthy === false && _jsx("em", { children: t('documents.missing') })] })] }, document.id)), appearance.surface === 'sidebar' && !loading && _jsx(ProgressiveFooter, { compact: true, visible: visibleItems.length, total: items.length, pageSize: pageSize, onMore: () => setVisibleLimit(value => value + pageSize) }), !loading && items.length === 0 && _jsxs("div", { className: css.documentListEmpty, children: [_jsx("span", { children: "\u25A4" }), _jsx("strong", { children: status === 'active' ? t('documents.emptyActive') : t('documents.emptyArchived') }), _jsx("p", { children: status === 'active' ? t('documents.emptyActiveText') : t('documents.emptyArchivedText') })] }), loading && _jsx("div", { className: css.loading, children: t('common.loading') })] }), _jsx("section", { ref: readerRef, className: css.documentReader, "aria-label": t('documents.reader'), "data-scroll-region": appearance.surface === 'sidebar' ? '' : undefined, children: selected === null ? _jsx(EmptyState, { glyph: "\u25A4", title: t('documents.selectTitle'), children: t('documents.selectText') }) : editing && appearance.surface === 'buildin' ? editComposer : _jsxs("article", { className: css.documentDetail, children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("span", { children: selected.status === 'active' ? t('documents.active') : t('documents.coldArchive') }), _jsx("h3", { children: selected.title }), _jsx("p", { children: selected.description || t('documents.noDescription') })] }), _jsx("div", { children: props.writeEnabled && selected.status === 'active' && _jsx("button", { type: "button", className: documentEditActionClass, onClick: beginEdit, children: t('documents.edit') }) })] }), _jsxs("dl", { children: [_jsxs("div", { children: [_jsx("dt", { children: t('documents.path') }), _jsx("dd", { children: _jsx("code", { children: selected.relativePath }) })] }), _jsxs("div", { children: [_jsx("dt", { children: t('documents.revision') }), _jsx("dd", { children: selected.revision })] }), _jsxs("div", { children: [_jsx("dt", { children: t('documents.hash') }), _jsx("dd", { children: _jsx("code", { children: selected.contentHash.slice(0, 16) }) })] }), _jsxs("div", { children: [_jsx("dt", { children: t('documents.size') }), _jsx("dd", { children: humanBytes(selected.sizeBytes) })] })] }), selected.sourcePaths.length > 0 && _jsxs("div", { className: css.documentSources, children: [_jsx("span", { children: t('documents.sources') }), selected.sourcePaths.map(path => _jsx("code", { children: path }, path))] }), selected.status === 'archived' && _jsxs("div", { className: css.documentArchiveReceipt, children: [_jsx("strong", { children: t('documents.archiveReceipt') }), _jsx("p", { children: selected.archiveSummary }), _jsx("div", { children: selected.memoryBodyIds.map(id => _jsx("code", { children: id }, id)) })] }), _jsx(DocumentMarkdown, { content: selected.content }), props.writeEnabled && selected.status === 'active' && _jsx("footer", { className: css.documentDanger, children: appearance.surface === 'buildin' && confirmArchive ? _jsxs(_Fragment, { children: [_jsx("span", { children: t('documents.archiveConfirm') }), _jsx("button", { type: "button", className: css.dangerSolidButton, disabled: saving, onClick: () => void archive(), children: saving ? t('documents.archiving') : t('documents.archiveNow') }), _jsx("button", { type: "button", className: css.ghostButton, onClick: () => setConfirmArchive(false), children: t('common.cancel') })] }) : _jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx("strong", { children: t('documents.archiveTitle') }), _jsx("p", { children: t('documents.archiveDescription') })] }), _jsx("button", { type: "button", className: documentArchiveActionClass, onClick: () => setConfirmArchive(true), children: t('documents.archive') })] }) })] }) })] }), _jsx("p", { className: css.runtimeFootnote, children: t('documents.footnote') }), composing && appearance.surface === 'sidebar' && _jsx(SidebarModal, { title: t('documents.newTitle'), description: t('documents.editorHint'), busy: saving, onClose: resetComposer, children: composer }), editing && appearance.surface === 'sidebar' && selected !== null && _jsx(SidebarModal, { title: t('documents.editTitle'), description: selected.title, busy: saving, onClose: () => setEditing(false), children: editComposer }), confirmArchive && appearance.surface === 'sidebar' && selected !== null && _jsx(SidebarModal, { title: t('documents.archiveConfirm'), description: selected.title, busy: saving, onClose: () => setConfirmArchive(false), children: _jsxs("div", { className: css.bodyDeleteConfirm, children: [_jsx("p", { children: t('documents.archiveDescription') }), _jsxs("div", { className: css.bodyDeleteSummary, children: [_jsx("strong", { children: selected.title }), _jsxs("span", { children: [selected.relativePath, " \u00B7 ", humanBytes(selected.sizeBytes)] })] }), _jsxs("div", { className: css.bodyEditActions, children: [_jsx("button", { type: "button", "data-autofocus": true, className: css.ghostButton, disabled: saving, onClick: () => setConfirmArchive(false), children: t('common.cancel') }), _jsx("button", { type: "button", className: css.dangerSolidButton, disabled: saving, onClick: () => void archive(), children: saving ? t('documents.archiving') : t('documents.archiveNow') })] })] }) })] }));
1335
- }
1336
- function versionModeLabel(t, mode) {
1337
- if (mode === 'homebrew')
1338
- return t('versions.modeHomebrew');
1339
- if (mode === 'go')
1340
- return t('versions.modeGo');
1341
- if (mode === 'npm')
1342
- return t('versions.modeNpm');
1343
- if (mode === 'link')
1344
- return t('versions.modeLink');
1345
- if (mode === 'missing')
1346
- return t('versions.modeMissing');
1347
- return t('versions.modeManual');
1348
- }
1349
- function versionHint(t, component) {
1350
- if (component.checkError !== undefined)
1351
- return t('versions.latestUnavailable');
1352
- if (component.updateHint === 'brew')
1353
- return t('versions.hintHomebrew');
1354
- if (component.updateHint === 'brew-missing')
1355
- return t('versions.hintBrewMissing');
1356
- if (component.updateHint === 'go')
1357
- return t('versions.hintGo');
1358
- if (component.updateHint === 'pnpm')
1359
- return t('versions.hintPnpm');
1360
- if (component.updateHint === 'pnpm-missing')
1361
- return t('versions.hintPnpmMissing');
1362
- if (component.updateHint === 'link')
1363
- return t('versions.hintLink');
1364
- if (component.updateHint === 'install')
1365
- return t('versions.hintInstall');
1366
- return t('versions.hintManual');
1367
- }
1368
- function dshInstallLabel(t, component) {
1369
- if (component.installMode === 'npm')
1370
- return t('versions.profileLocation', { name: component.installProfile ?? '—' });
1371
- if (component.installMode === 'link')
1372
- return component.installProfile === undefined
1373
- ? t('versions.sourceLocation')
1374
- : t('versions.linkSourceLocation', { name: component.installProfile });
1375
- return t('versions.packageLocation');
1376
- }
1377
- function VersionDialog(props) {
1378
- const t = useT();
1379
- const [snapshot, setSnapshot] = useState(null);
1380
- const [checking, setChecking] = useState(true);
1381
- const [updating, setUpdating] = useState(null);
1382
- const [error, setError] = useState(null);
1383
- const [result, setResult] = useState(null);
1384
- const check = useCallback(async () => {
1385
- setChecking(true);
1386
- setError(null);
1387
- let timeout;
1388
- try {
1389
- const deadline = new Promise((_resolve, reject) => { timeout = setTimeout(() => reject(new Error(t('versions.timeout'))), 15_000); });
1390
- setSnapshot(await Promise.race([props.client.versions(), deadline]));
1391
- }
1392
- catch (reason) {
1393
- setError(message(reason));
1394
- }
1395
- finally {
1396
- if (timeout !== undefined)
1397
- clearTimeout(timeout);
1398
- setChecking(false);
1399
- }
1400
- }, [props.client, t]);
1401
- useEffect(() => { void check(); }, [check]);
1402
- const update = async (component) => {
1403
- setUpdating(component.id);
1404
- setError(null);
1405
- setResult(null);
1406
- try {
1407
- const next = await props.client.updateVersion(component.id);
1408
- setResult(next);
1409
- await check();
1410
- props.onRefreshStatus();
1411
- }
1412
- catch (reason) {
1413
- setError(message(reason));
1414
- }
1415
- finally {
1416
- setUpdating(null);
1417
- }
1418
- };
1419
- const busy = checking || updating !== null;
1420
- return _jsx(SidebarModal, { title: t('versions.title'), description: t('versions.description'), busy: busy, onClose: props.onClose, children: _jsxs("div", { className: css.versionDialogBody, children: [checking && snapshot === null && _jsxs("div", { className: css.versionChecking, role: "status", children: [_jsx("span", {}), t('versions.checking')] }), error !== null && _jsxs("div", { className: css.versionError, role: "alert", children: [_jsx("strong", { children: t('versions.failed') }), _jsx("p", { children: error })] }), result !== null && _jsxs("div", { className: css.versionResult, role: "status", children: [_jsx("strong", { children: result.updated ? t('versions.updated', { name: result.component === 'mnemon' ? 'Mnemon CLI' : 'dsh-mnemon' }) : t('versions.alreadyCurrent') }), result.restartRequired && _jsx("p", { children: t('versions.restartRequired') })] }), snapshot !== null && _jsx("div", { className: css.versionList, children: snapshot.components.map(component => {
1421
- const canUpdate = component.outdated && component.updateSupported && component.checkError === undefined;
1422
- const state = component.checkError !== undefined ? t('versions.unknown') : component.outdated ? t('versions.available') : t('versions.current');
1423
- return _jsxs("article", { "data-outdated": component.outdated || undefined, children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("strong", { children: component.name }), _jsx("span", { children: versionModeLabel(t, component.installMode) })] }), _jsx("em", { children: state })] }), _jsxs("div", { className: css.versionNumbers, children: [_jsxs("div", { children: [_jsx("small", { children: t('versions.installed') }), _jsx("code", { children: component.current ?? '—' })] }), _jsx("span", { children: "\u2192" }), _jsxs("div", { children: [_jsx("small", { children: t('versions.latest') }), _jsx("code", { children: component.latest ?? '—' })] })] }), component.id === 'mnemon' && component.executablePath !== undefined && _jsxs("small", { className: css.versionLocation, title: component.executablePath, children: [_jsx("span", { children: t('versions.executable') }), _jsx("code", { children: component.executablePath })] }), component.id === 'dsh-mnemon' && component.installPath !== undefined && _jsxs("small", { className: css.versionLocation, title: component.installPath, children: [_jsx("span", { children: dshInstallLabel(t, component) }), _jsx("code", { children: component.installPath })] }), _jsxs("footer", { children: [_jsx("p", { children: versionHint(t, component) }), canUpdate && _jsx("button", { type: "button", className: css.primaryButton, disabled: busy, onClick: () => void update(component), children: updating === component.id ? t('versions.updating') : t('versions.update') })] })] }, component.id);
1424
- }) }), _jsxs("div", { className: css.versionDialogActions, children: [_jsx("span", { children: snapshot === null ? '' : t('versions.checkedAt', { time: new Date(snapshot.checkedAt).toLocaleTimeString() }) }), _jsxs("div", { children: [_jsx("button", { type: "button", className: css.ghostButton, disabled: busy, onClick: props.onClose, children: t('common.cancel') }), _jsx("button", { type: "button", "data-autofocus": true, className: css.secondaryButton, disabled: busy, onClick: () => void check(), children: checking ? t('versions.checkingShort') : t('versions.recheck') })] })] })] }) });
1425
- }
1426
- function StatusPage(props) {
1427
- const t = useT();
1428
- const [versionsOpen, setVersionsOpen] = useState(false);
1429
- const status = props.status;
1430
- const documents = status?.documents;
1431
- const catalogKnown = status?.memoryBodies !== undefined;
1432
- const memoryBodies = useMemo(() => status?.memoryBodies ?? [], [status]);
1433
- const activeBodies = memoryBodies.filter(body => body.active).length;
1434
- const storage = status?.storage;
1435
- const selectedScopeKind = storage?.activeKind ?? 'global';
1436
- const selectedScope = storage?.scopes.find(scope => scope.kind === selectedScopeKind);
1437
- const runtimeArea = selectedScope?.areas.find(area => area.kind === 'runtime');
1438
- const runtimeUserEntries = runtimeArea === undefined ? 0 : Number(runtimeArea.details.userEntries ?? 0);
1439
- const runtimeMemoryEntries = runtimeArea === undefined ? 0 : Number(runtimeArea.details.memoryEntries ?? 0);
1440
- return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('status.title'), description: t('status.description'), meta: status === null && props.loading ? t('common.loading') : status?.healthy === true ? t('status.nominal') : t('status.checkRequired'), action: _jsxs("div", { className: css.statusHeaderActions, children: [_jsx("button", { type: "button", className: css.ghostButton, disabled: props.loading, onClick: props.onRefresh, children: props.loading ? t('status.rechecking') : t('status.recheck') }), _jsx("button", { type: "button", className: css.secondaryButton, onClick: () => setVersionsOpen(true), children: t('versions.checkAction') })] }) }), _jsxs("section", { className: css.healthStrip, "aria-label": t('status.aria'), children: [_jsxs("article", { children: [_jsx("span", { className: `${css.healthIndicator} ${status === null ? css.healthMuted : status.healthy ? css.healthGood : css.healthBad}` }), _jsxs("div", { children: [_jsx("small", { children: t('status.engine') }), _jsx("strong", { children: status === null ? t('status.engineChecking') : status.healthy ? status.version === undefined ? t('status.engineConnected') : `Mnemon ${status.version}` : t('status.engineUnavailable') }), _jsx("p", { children: status?.dshMnemonVersion === undefined ? t('status.versionWaiting') : `dsh-mnemon ${status.dshMnemonVersion}` })] })] }), _jsxs("article", { children: [_jsx("span", { className: `${css.healthIndicator} ${runtimeArea === undefined ? css.healthMuted : runtimeArea.status === 'invalid' ? css.healthBad : css.healthGood}` }), _jsxs("div", { children: [_jsx("small", { children: t('status.runtime') }), _jsx("strong", { children: runtimeArea === undefined ? t('status.runtimeWaiting') : t('status.runtimeRatio', { user: runtimeUserEntries, memory: runtimeMemoryEntries }) }), _jsx("p", { children: runtimeArea === undefined ? t('status.runtimeWaitingDetail') : t('status.runtimeBytes', { bytes: humanBytes(runtimeArea.bytes) }) })] })] }), _jsxs("article", { children: [_jsx("span", { className: `${css.healthIndicator} ${activeBodies > 0 ? css.healthGood : css.healthMuted}` }), _jsxs("div", { children: [_jsx("small", { children: t('status.spaces') }), _jsx("strong", { children: catalogKnown ? t('status.activeRatio', { active: activeBodies, total: memoryBodies.length }) : t('status.directoryUnsynced') }), _jsx("p", { children: t('status.activeMemories', { count: status?.stats?.totalInsights ?? 0 }) })] })] }), _jsxs("article", { children: [_jsx("span", { className: `${css.healthIndicator} ${documents === undefined ? css.healthMuted : css.healthGood}` }), _jsxs("div", { children: [_jsx("small", { children: t('status.documents') }), _jsx("strong", { children: documents === undefined ? t('status.documentsWaiting') : t('status.documentRatio', { active: documents.activeCount, archived: documents.archivedCount }) }), _jsx("p", { children: documents === undefined ? t('status.documentsSession') : t('status.documentUsage', { used: humanBytes(documents.activeBytes), limit: humanBytes(documents.limitBytes) }) })] })] })] }), _jsx(StorageDomains, { catalog: storage, selected: selectedScope, selectedKind: selectedScopeKind }), versionsOpen && _jsx(VersionDialog, { client: props.client, onClose: () => setVersionsOpen(false), onRefreshStatus: props.onRefresh })] }));
1441
- }
1442
- function storageScopeLabel(t, kind) {
1443
- return t(kind === 'global' ? 'status.storageGlobal' : kind === 'workspace' ? 'status.storageWorkspace' : 'status.storageCustom');
1444
- }
1445
- /** Resolve the configured scope before the first status round-trip to keep the Sidebar header stable. */
1446
- function configuredStorageScope(config) {
1447
- return config?.storageScope ?? (config?.dataDir?.trim() ? 'custom' : 'global');
1448
- }
1449
- function storageAreaLabel(t, kind) {
1450
- return t(kind === 'runtime' ? 'status.storageRuntime' : kind === 'memory-bodies' ? 'status.storageBodies' : kind === 'documents' ? 'status.storageDocuments' : 'status.storageState');
1451
- }
1452
- function storageAreaDetails(t, area) {
1453
- if (area.kind === 'runtime')
1454
- return t('status.storageRuntimeDetail', { user: area.details.userEntries ?? 0, memory: area.details.memoryEntries ?? 0 });
1455
- if (area.kind === 'memory-bodies')
1456
- return t('status.storageBodiesDetail', { active: area.details.activeBodies ?? 0, databases: area.details.databases ?? 0 });
1457
- if (area.kind === 'documents')
1458
- return t('status.storageDocumentsDetail', { active: area.details.activeDocuments ?? 0, archived: area.details.archivedDocuments ?? 0 });
1459
- return area.details.reviewLedger === true ? t('status.storageStateReady') : t('status.storageStateVolatile');
1460
- }
1461
- function StorageDomains(props) {
1462
- const t = useT();
1463
- const areaStatus = (status) => t(status === 'ready' ? 'status.storageReady' : status === 'empty' ? 'status.storageEmpty' : status === 'missing' ? 'status.storageMissing' : 'status.storageInvalid');
1464
- return (_jsxs("section", { className: css.storageDomains, "aria-label": t('status.storageDomains'), children: [_jsxs("div", { className: css.statusSectionHeader, children: [_jsxs("div", { children: [_jsx("h3", { children: t('status.storageDomains') }), _jsx("p", { children: t('status.storageDomainsText') })] }), _jsx("span", { className: css.phaseBadge, children: storageScopeLabel(t, props.selectedKind) })] }), props.catalog === undefined ? _jsx("div", { className: css.storageUnavailable, children: t('status.storageWaiting') }) : props.selected?.root === undefined ? _jsxs("div", { className: css.storageUnavailable, children: [_jsx("strong", { children: storageScopeLabel(t, props.selectedKind) }), _jsx("p", { children: props.selectedKind === 'custom' ? t('status.storageCustomUnset') : t('status.storageWorkspaceUnavailable') })] }) : _jsxs(_Fragment, { children: [_jsxs("div", { className: css.storageRoot, children: [_jsxs("div", { children: [_jsxs("span", { children: [storageScopeLabel(t, props.selectedKind), " \u00B7 ", t('status.storageActiveRoot')] }), _jsx("code", { children: props.selected.root })] }), _jsxs("div", { children: [_jsx("strong", { children: humanBytes(props.selected.totalBytes) }), _jsx("small", { children: props.selected.available ? t('status.storageAvailable') : t('status.storageNotCreated') })] })] }), _jsx("div", { className: css.storageAreaGrid, children: props.selected.areas.filter(area => area.kind !== 'state').map(area => _jsxs("article", { "data-status": area.status, children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("span", {}), " ", _jsx("strong", { children: storageAreaLabel(t, area.kind) })] }), _jsx("em", { children: areaStatus(area.status) })] }), _jsxs("div", { className: css.storageAreaMetric, children: [_jsx("strong", { children: area.itemCount }), _jsx("span", { children: t('status.storageItems') }), _jsx("code", { children: humanBytes(area.bytes) })] }), _jsx("p", { children: storageAreaDetails(t, area) }), _jsx("code", { className: css.storagePath, children: area.path }), area.issue !== undefined && _jsx("small", { children: area.issue })] }, area.kind)) })] }), props.catalog !== undefined && _jsx("p", { className: css.storageFootnote, children: t('status.storageFootnote', { root: props.catalog.activeRoot }) })] }));
1465
- }
1466
- export function MnemonView(props) {
1467
- const t = props.t ?? translateZh;
1468
- const appearance = resolveMnemonViewAppearance(props.surface ?? 'buildin', t);
1469
- return _jsx(I18nContext.Provider, { value: t, children: _jsx(LocaleContext.Provider, { value: props.locale ?? 'zh', children: _jsx(MnemonViewAppearanceProvider, { value: appearance, children: _jsx(MnemonWorkspace, { ...props }) }) }) });
1470
- }
1471
- function MnemonWorkspace({ connection, settingsScope, sessionId, workspaceId, workspaceSelection, onClose }) {
1472
- const t = useT();
1473
- const appearance = useMnemonViewAppearance();
1474
- const settingsSnapshot = useSyncExternalStore(settingsScope.subscribe, settingsScope.getSnapshot, settingsScope.getSnapshot);
1475
- const client = useMemo(() => new MnemonClient(connection, sessionId, workspaceId), [connection, sessionId, workspaceId]);
1476
- const clientContextKey = `${sessionId ?? ''}\u0000${workspaceId ?? ''}`;
1477
- const viewContextKey = `${clientContextKey}\u0000${settingsSnapshot.revision ?? 'loading'}`;
1478
- const [page, setPage] = useState('status');
1479
- const lastMemoryPage = useRef('overview');
1480
- const canvasRef = useRef(null);
1481
- const selectPage = useCallback((next) => {
1482
- if (isMemoryPage(next))
1483
- lastMemoryPage.current = next;
1484
- setPage(next);
1485
- }, []);
1486
- const selectPrimaryPage = useCallback((next) => {
1487
- selectPage(appearance.surface === 'sidebar' && next === 'overview' ? lastMemoryPage.current : next);
1488
- }, [appearance.surface, selectPage]);
1489
- /** Pages share one plugin-owned scroll container; never mutate DSH ancestor scrollports. */
1490
- const resetViewportScroll = useCallback(() => {
1491
- const canvas = canvasRef.current;
1492
- if (canvas !== null)
1493
- canvas.scrollTop = 0;
1494
- }, []);
1495
- // Reset before paint so a newly selected page never flashes at the previous
1496
- // page's scroll offset for one frame. The host still owns every ancestor.
1497
- useLayoutEffect(() => { resetViewportScroll(); }, [viewContextKey, page, resetViewportScroll]);
1498
- const [statusState, setStatusState] = useState(() => ({ contextKey: viewContextKey, value: null, loading: true, error: null }));
1499
- const currentStatusState = statusState.contextKey === viewContextKey
1500
- ? statusState
1501
- : { contextKey: viewContextKey, value: null, loading: true, error: null };
1502
- const status = currentStatusState.value;
1503
- const statusLoading = currentStatusState.loading;
1504
- const statusError = currentStatusState.error;
1505
- const statusRequest = useRef(0);
1506
- const [revision, setRevision] = useState(0);
1507
- const [searchSeed, setSearchSeed] = useState('');
1508
- const [rememberSeed, setRememberSeed] = useState('');
1509
- const [rememberOpen, setRememberOpen] = useState(false);
1510
- // A newly inspected workspace must never inherit visible cards, open editors,
1511
- // search seeds, or scroll position from the previous workspace.
1512
- useLayoutEffect(() => {
1513
- setRememberOpen(false);
1514
- setRememberSeed('');
1515
- setSearchSeed('');
1516
- }, [viewContextKey]);
1517
- const openRemember = useCallback((seed = '') => {
1518
- setRememberSeed(seed);
1519
- setRememberOpen(true);
1520
- }, []);
1521
- /** Conversation surfaces ask this view to open a page (optionally with a seed). */
1522
- const applyAnchor = useCallback((anchor) => {
1523
- if (anchor.page === 'remember' && appearance.surface === 'sidebar') {
1524
- openRemember(anchor.seed ?? '');
1525
- selectPage(lastMemoryPage.current);
1526
- return;
1527
- }
1528
- if (anchor.seed !== undefined && anchor.seed !== '') {
1529
- if (anchor.page === 'explore')
1530
- setSearchSeed(anchor.seed);
1531
- if (anchor.page === 'remember')
1532
- setRememberSeed(anchor.seed);
1533
- }
1534
- selectPage(anchor.page);
1535
- }, [appearance.surface, openRemember, selectPage]);
1536
- useEffect(() => {
1537
- const held = consumeMnemonAnchor(sessionId);
1538
- if (held !== null)
1539
- applyAnchor(held);
1540
- return subscribeMnemonAnchor(sessionId, applyAnchor);
1541
- }, [sessionId, applyAnchor]);
1542
- const loadStatus = useCallback(async () => {
1543
- const request = ++statusRequest.current;
1544
- setStatusState(current => ({ contextKey: viewContextKey, value: current.contextKey === viewContextKey ? current.value : null, loading: true, error: null }));
1545
- try {
1546
- const next = await client.status();
1547
- if (request === statusRequest.current)
1548
- setStatusState({ contextKey: viewContextKey, value: next, loading: false, error: null });
1549
- }
1550
- catch (reason) {
1551
- if (request === statusRequest.current)
1552
- setStatusState({ contextKey: viewContextKey, value: null, loading: false, error: message(reason) });
1553
- }
1554
- }, [client, viewContextKey]);
1555
- useEffect(() => { void loadStatus(); }, [loadStatus]);
1556
- const mutate = useCallback(() => { setRevision(value => value + 1); void loadStatus(); }, [loadStatus]);
1557
- const forget = useCallback(async (insight) => { await client.forget(insight.id, insight.memoryBodyId); mutate(); }, [client, mutate]);
1558
- const explore = useCallback((query) => { setSearchSeed(query); selectPage('explore'); }, [selectPage]);
1559
- const clone = useCallback((insight) => {
1560
- if (appearance.surface === 'sidebar')
1561
- openRemember(insight.content);
1562
- else {
1563
- setRememberSeed(insight.content);
1564
- selectPage('remember');
1565
- }
1566
- }, [appearance.surface, openRemember, selectPage]);
1567
- const refreshAll = () => { setRevision(value => value + 1); void loadStatus(); };
1568
- const writeEnabled = status?.writeEnabled === true;
1569
- const stats = status?.stats;
1570
- const catalogKnown = status?.memoryBodies !== undefined;
1571
- const memoryBodies = useMemo(() => status?.memoryBodies ?? [], [status]);
1572
- const activeBodies = memoryBodies.filter(body => body.active).length;
1573
- const workspaceContext = status?.workspaceContext;
1574
- const storageMode = workspaceContext?.mode ?? status?.storage?.activeKind ?? configuredStorageScope(settingsSnapshot.value);
1575
- const storageModeText = storageScopeLabel(t, storageMode);
1576
- const showWorkspacePicker = storageMode === 'workspace' && workspaceSelection !== undefined && workspaceSelection.options.length > 0;
1577
- const workspaceDiverged = workspaceContext?.mode === 'workspace' && !workspaceContext.aligned;
1578
- const canAlignWorkspace = workspaceDiverged && workspaceSelection?.effectiveWorkspaceId !== undefined;
1579
- const workspaceDifference = workspaceContext === undefined
1580
- ? ''
1581
- : `${t('workspace.selectedRoot', { root: workspaceContext.selectedRoot })}; ${t('workspace.effectiveRoot', { root: workspaceContext.effectiveRoot })}`;
1582
- const workspacePicker = showWorkspacePicker && _jsxs("label", { className: appearanceClass(css.workspacePicker, appearance.classes.workspacePicker), children: [_jsx("span", { children: t('workspace.viewing') }), _jsx("select", { "aria-label": t('workspace.selectorAria'), value: workspaceSelection.selectedWorkspaceId ?? '', onChange: event => workspaceSelection.onSelect(event.target.value), children: workspaceSelection.options.map(workspace => _jsx("option", { value: workspace.id, children: workspace.title }, workspace.id)) })] });
1583
- const connectionLabel = statusLoading
1584
- ? t('header.checking')
1585
- : status?.healthy !== true
1586
- ? t('header.unavailable')
1587
- : appearance.surface === 'sidebar'
1588
- ? t('header.connected')
1589
- : catalogKnown
1590
- ? t('header.connectedWithCount', { count: activeBodies })
1591
- : t('header.directoryPending');
1592
- return (_jsxs("main", { className: appearanceClass(css.shell, appearance.classes.shell), "data-mnemon-surface": appearance.surface, children: [_jsxs("header", { className: appearanceClass(css.masthead, appearance.classes.masthead), children: [appearance.surface === 'sidebar' && onClose !== undefined && _jsxs("button", { type: "button", className: appearanceClass(css.ghostButton, css.backButton), onClick: onClose, "aria-label": t('header.backToConversation'), children: [_jsx(IconChevronLeftOutline14, { size: 14 }), _jsx("span", { children: t('header.backToConversation') })] }), _jsxs("div", { className: appearanceClass(css.brand, appearance.classes.brand), children: [appearance.showLogo && _jsx(MnemonLogo, { className: css.brandLogo }), _jsx("h1", { children: appearance.title }), appearance.surface === 'sidebar' && _jsxs("span", { className: css.storageMode, "aria-label": t('workspace.storageModeAria', { mode: storageModeText }), children: [_jsx("span", { children: t('workspace.storageMode') }), _jsx("strong", { children: storageModeText })] }), appearance.surface === 'sidebar' && workspacePicker, appearance.surface === 'sidebar' && canAlignWorkspace && _jsxs("div", { className: appearanceClass(css.workspaceMismatch, appearance.classes.workspaceMismatch), role: "status", "aria-label": `${t('workspace.mismatchTitle')}. ${workspaceDifference}`, title: workspaceDifference, children: [_jsx("span", { children: t('workspace.mismatchShort') }), _jsx("button", { type: "button", onClick: workspaceSelection.onAlign, children: t('workspace.align') })] })] }), appearance.showTelemetry && _jsxs("section", { className: css.telemetry, "aria-label": t('telemetry.aria'), children: [_jsxs("div", { className: css.telemetryMetric, children: [_jsx("span", { children: t('telemetry.memories') }), _jsx("strong", { children: stats?.totalInsights ?? '—' })] }), _jsxs("div", { className: css.telemetryMetric, children: [_jsx("span", { children: t('telemetry.graph') }), _jsx("strong", { children: stats?.edgeCount ?? '—' })] }), _jsxs("div", { className: css.telemetryMetric, children: [_jsx("span", { children: t('telemetry.entities') }), _jsx("strong", { children: stats?.topEntities.length ?? '—' })] }), _jsxs("div", { className: css.telemetryMetric, children: [_jsx("span", { children: t('telemetry.spaces') }), _jsx("strong", { children: status === null || !catalogKnown ? '—' : activeBodies })] })] }), _jsxs("div", { className: appearanceClass(css.headerActions, appearance.classes.headerActions), children: [appearance.surface === 'buildin' && workspacePicker, _jsxs("div", { className: appearanceClass(css.statusCluster, appearance.classes.statusCluster), children: [_jsx("span", { className: `${css.statusDot} ${statusLoading && status === null ? css.checking : status?.healthy === true ? css.online : css.offline}` }), _jsx("span", { children: connectionLabel }), _jsx("button", { type: "button", className: css.iconButton, disabled: statusLoading, onClick: refreshAll, "aria-label": t('common.refresh'), children: "\u21BB" })] })] })] }), (statusError !== null || status?.healthy === false) && _jsxs("div", { className: css.alert, role: "alert", children: [_jsx("strong", { children: t('header.notReady') }), _jsx("span", { children: statusError ?? status?.error })] }), appearance.surface === 'buildin' && workspaceDiverged && _jsxs("div", { className: css.workspaceMismatch, role: "status", children: [_jsxs("div", { children: [_jsx("strong", { children: t('workspace.mismatchTitle') }), _jsx("span", { children: t('workspace.mismatchDescription') }), _jsxs("div", { children: [_jsx("code", { children: t('workspace.selectedRoot', { root: workspaceContext.selectedRoot }) }), _jsx("code", { children: t('workspace.effectiveRoot', { root: workspaceContext.effectiveRoot }) })] })] }), canAlignWorkspace && _jsx("button", { type: "button", className: css.secondaryButton, onClick: workspaceSelection.onAlign, children: t('workspace.align') })] }), _jsxs("div", { className: css.workspace, children: [_jsx(WorkspaceNavigation, { page: page, onSelect: selectPrimaryPage, activeBodies: activeBodies, bodyCount: memoryBodies.length, catalogKnown: catalogKnown, writeEnabled: writeEnabled }), _jsx(MemoryNavigation, { page: page, writeEnabled: writeEnabled, onSelect: selectPage, onRemember: () => openRemember() }), _jsxs("section", { className: appearanceClass(css.canvas, appearance.classes.canvas), ref: canvasRef, "data-testid": "mnemon-canvas", "data-lock-page-header": !isMemoryPage(page) ? '' : undefined, children: [page === 'overview' && _jsx(OverviewPage, { client: client, revision: revision, writeEnabled: writeEnabled, fallbackBodies: memoryBodies, fallbackDirectory: status?.memoryBodyDirectory, catalogKnown: catalogKnown, onMutate: mutate, onExplore: explore }), page === 'runtime' && _jsx(RuntimePage, { client: client, revision: revision, writeEnabled: writeEnabled, onMutate: mutate }), page === 'documents' && _jsx(DocumentsPage, { client: client, revision: revision, writeEnabled: writeEnabled, ...(sessionId === undefined ? {} : { sessionId }), onMutate: mutate }), page === 'explore' && _jsx(ExplorePage, { client: client, status: status, seed: searchSeed, writeEnabled: writeEnabled, onForget: forget }), page === 'entities' && _jsx(EntitiesPage, { client: client, revision: revision, writeEnabled: writeEnabled, onForget: forget, onExplore: explore }), page === 'remember' && appearance.surface === 'buildin' && _jsx(RememberPage, { client: client, sessionId: sessionId, memoryBodies: memoryBodies, writeEnabled: writeEnabled, seed: rememberSeed, onMutate: mutate }), page === 'list' && _jsx(ListPage, { client: client, revision: revision, writeEnabled: writeEnabled, onForget: forget, onClone: clone, onExplore: explore }), page === 'status' && _jsx(StatusPage, { client: client, status: status, loading: statusLoading, onRefresh: () => void loadStatus() })] }, viewContextKey), appearance.surface === 'sidebar' && rememberOpen && _jsx(RememberPage, { client: client, sessionId: sessionId, memoryBodies: memoryBodies, writeEnabled: writeEnabled, seed: rememberSeed, onMutate: mutate, onClose: () => setRememberOpen(false), onComplete: () => setRememberOpen(false) })] })] }));
1593
- }
1594
- //# sourceMappingURL=MnemonView.js.map