@abstractframework/monitor-active-memory 0.1.0 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # @abstractuic/monitor-active-memory
1
+ # @abstractframework/monitor-active-memory
2
2
 
3
3
  ReactFlow-based explorer for **Knowledge Graph assertions** (`KgAssertion`) and the derived **Active Memory** text.
4
4
 
@@ -20,13 +20,13 @@ Declared in `monitor-active-memory/package.json`:
20
20
 
21
21
  ## Install
22
22
 
23
- - Workspace: add a dependency on `@abstractuic/monitor-active-memory`
24
- - npm (once published): `npm i @abstractuic/monitor-active-memory`
23
+ - Workspace: add a dependency on `@abstractframework/monitor-active-memory`
24
+ - npm (once published): `npm i @abstractframework/monitor-active-memory`
25
25
 
26
26
  ## Usage
27
27
 
28
28
  ```tsx
29
- import { KgActiveMemoryExplorer, type KgAssertion } from "@abstractuic/monitor-active-memory";
29
+ import { KgActiveMemoryExplorer, type KgAssertion } from "@abstractframework/monitor-active-memory";
30
30
 
31
31
  const items: KgAssertion[] = [];
32
32
 
@@ -62,15 +62,21 @@ The component can persist per-view layouts in `localStorage` under key `abstract
62
62
 
63
63
  ## CSS
64
64
 
65
- - ReactFlow base styles are **not** imported by this package. In your app:
65
+ - Import CSS in your app entrypoint (recommended):
66
66
 
67
67
  ```ts
68
- import "reactflow/dist/style.css";
68
+ import "@abstractframework/monitor-active-memory/styles.css";
69
+ import "@abstractframework/ui-kit/theme.css"; // shared tokens (optional but recommended)
69
70
  ```
70
71
 
71
- - This package imports `monitor-active-memory/src/styles.css` internally.
72
+ - ReactFlow base styles are **not** included. In your app:
73
+
74
+ ```ts
75
+ import "reactflow/dist/style.css";
76
+ ```
72
77
 
73
78
  ## Related docs
74
79
 
75
80
  - Getting started: [`docs/getting-started.md`](../docs/getting-started.md)
81
+ - API reference: [`docs/api.md`](../docs/api.md)
76
82
  - Architecture: [`docs/architecture.md`](../docs/architecture.md)
@@ -1,5 +1,4 @@
1
1
  import type { JsonValue, KgAssertion, KgQueryParams, KgQueryResult } from './types';
2
- import './styles.css';
3
2
  export interface KgActiveMemoryExplorerProps {
4
3
  title?: string;
5
4
  resetKey?: string;
@@ -29,5 +28,5 @@ export interface KgActiveMemoryExplorerProps {
29
28
  assertion: KgAssertion;
30
29
  }) => void;
31
30
  }
32
- export declare function KgActiveMemoryExplorer({ title, resetKey, queryMode, items, activeMemoryText, packets, packetsVersion, packedCount, dropped, estimatedTokens, effort, warnings, onQuery, onItemsReplace, onOpenSpan, onOpenTranscript, }: KgActiveMemoryExplorerProps): import("react/jsx-runtime").JSX.Element;
31
+ export declare function KgActiveMemoryExplorer({ resetKey, queryMode, items, activeMemoryText, packets, packetsVersion, packedCount, dropped, estimatedTokens, effort, warnings, onQuery, onItemsReplace, onOpenSpan, onOpenTranscript, }: KgActiveMemoryExplorerProps): import("react/jsx-runtime").JSX.Element;
33
32
  //# sourceMappingURL=KgActiveMemoryExplorer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"KgActiveMemoryExplorer.d.ts","sourceRoot":"","sources":["../src/KgActiveMemoryExplorer.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAA4B,MAAM,SAAS,CAAC;AAC9G,OAAO,cAAc,CAAC;AAEtB,MAAM,WAAW,2BAA2B;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IACnC,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;IAC5D,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE;QAAE,IAAI,EAAE,YAAY,GAAG,uBAAuB,CAAC;QAAC,MAAM,EAAE,aAAa,CAAA;KAAE,KAAK,IAAI,CAAC;IAC/H,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,WAAW,CAAA;KAAE,KAAK,IAAI,CAAC;IACzF,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,WAAW,CAAA;KAAE,KAAK,IAAI,CAAC;CACjG;AAgSD,wBAAgB,sBAAsB,CAAC,EACrC,KAAK,EACL,QAAQ,EACR,SAAS,EACT,KAAK,EACL,gBAAgB,EAChB,OAAO,EACP,cAAc,EACd,WAAW,EACX,OAAO,EACP,eAAe,EACf,MAAM,EACN,QAAQ,EACR,OAAO,EACP,cAAc,EACd,UAAU,EACV,gBAAgB,GACjB,EAAE,2BAA2B,2CAypD7B"}
1
+ {"version":3,"file":"KgActiveMemoryExplorer.d.ts","sourceRoot":"","sources":["../src/KgActiveMemoryExplorer.tsx"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAA4B,MAAM,SAAS,CAAC;AAE9G,MAAM,WAAW,2BAA2B;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IACnC,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;IAC5D,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE;QAAE,IAAI,EAAE,YAAY,GAAG,uBAAuB,CAAC;QAAC,MAAM,EAAE,aAAa,CAAA;KAAE,KAAK,IAAI,CAAC;IAC/H,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,WAAW,CAAA;KAAE,KAAK,IAAI,CAAC;IACzF,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,WAAW,CAAA;KAAE,KAAK,IAAI,CAAC;CACjG;AA0TD,wBAAgB,sBAAsB,CAAC,EACrC,QAAQ,EACR,SAAS,EACT,KAAK,EACL,gBAAgB,EAChB,OAAO,EACP,cAAc,EACd,WAAW,EACX,OAAO,EACP,eAAe,EACf,MAAM,EACN,QAAQ,EACR,OAAO,EACP,cAAc,EACd,UAAU,EACV,gBAAgB,GACjB,EAAE,2BAA2B,2CAu1D7B"}
@@ -1,8 +1,7 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
- import ReactFlow, { Background, Controls, MarkerType, MiniMap, Panel, ReactFlowProvider } from 'reactflow';
3
+ import ReactFlow, { Background, ControlButton, Controls, MarkerType, MiniMap, Panel, ReactFlowProvider, } from 'reactflow';
4
4
  import { buildKgGraph, buildKgLayout, forceSimulationEnergy, forceSimulationPositions, hashStringToSeed, initForceSimulation, sanitizeViewport, shortestPath, stepForceSimulation, } from './graph';
5
- import './styles.css';
6
5
  function normalizeScope(value, fallback = 'session') {
7
6
  const s = String(value ?? '')
8
7
  .trim()
@@ -23,6 +22,20 @@ function isCompactViewport() {
23
22
  return false;
24
23
  }
25
24
  }
25
+ function LegendIcon() {
26
+ return (_jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", focusable: "false", children: [_jsx("rect", { x: "3", y: "3", width: "3", height: "3", rx: "1" }), _jsx("rect", { x: "3", y: "7", width: "3", height: "3", rx: "1" }), _jsx("rect", { x: "3", y: "11", width: "3", height: "3", rx: "1" }), _jsx("rect", { x: "8", y: "3.5", width: "6", height: "2", rx: "1", fill: "none", strokeWidth: "1.4" }), _jsx("rect", { x: "8", y: "7.5", width: "6", height: "2", rx: "1", fill: "none", strokeWidth: "1.4" }), _jsx("rect", { x: "8", y: "11.5", width: "6", height: "2", rx: "1", fill: "none", strokeWidth: "1.4" })] }));
27
+ }
28
+ function clampNumber(value, lo, hi, fallback) {
29
+ const n = typeof value === 'number' && Number.isFinite(value) ? value : fallback;
30
+ return Math.min(hi, Math.max(lo, n));
31
+ }
32
+ function forceOptionsForSpread(spread) {
33
+ const s = clampNumber(spread, 0.6, 2.4, 1);
34
+ // Keep defaults in `graph.ts` as the baseline.
35
+ const springLength = 240 * s;
36
+ const repulsionStrength = 9000 * s * s;
37
+ return { springLength, repulsionStrength };
38
+ }
26
39
  const AMX_LAYOUT_STORAGE_KEY = 'abstractuic_amx_saved_layouts_v1';
27
40
  const AMX_VIEWPORT_MIN_ZOOM = 0.025;
28
41
  const AMX_VIEWPORT_MAX_ZOOM = 6;
@@ -305,7 +318,7 @@ function predicateSummary(assertions, opts = {}) {
305
318
  const more = preds.length > max ? ` +${preds.length - max}` : '';
306
319
  return `${label}${more}`.trim();
307
320
  }
308
- export function KgActiveMemoryExplorer({ title, resetKey, queryMode, items, activeMemoryText, packets, packetsVersion, packedCount, dropped, estimatedTokens, effort, warnings, onQuery, onItemsReplace, onOpenSpan, onOpenTranscript, }) {
321
+ export function KgActiveMemoryExplorer({ resetKey, queryMode, items, activeMemoryText, packets, packetsVersion, packedCount, dropped, estimatedTokens, effort, warnings, onQuery, onItemsReplace, onOpenSpan, onOpenTranscript, }) {
309
322
  const flowRef = useRef(null);
310
323
  const stepSig = useMemo(() => {
311
324
  const first = items?.[0];
@@ -342,14 +355,22 @@ export function KgActiveMemoryExplorer({ title, resetKey, queryMode, items, acti
342
355
  });
343
356
  const compactViewport = miniMapDefaults.compact;
344
357
  const [showMiniMap, setShowMiniMap] = useState(miniMapDefaults.show);
358
+ const [showLegend, setShowLegend] = useState(false);
345
359
  const layoutKey = resetSig;
346
360
  const defaultLayoutSeed = useMemo(() => hashStringToSeed(layoutKey), [layoutKey]);
347
361
  const [layoutKind, setLayoutKind] = useState('grid');
348
362
  const [layoutSeed, setLayoutSeed] = useState(defaultLayoutSeed);
349
363
  const [layoutPlaying, setLayoutPlaying] = useState(false);
364
+ const [layoutSpread, setLayoutSpread] = useState(1.0);
350
365
  const simRef = useRef(null);
351
366
  const pendingViewportRef = useRef(null);
352
367
  const pendingFitViewRef = useRef(false);
368
+ const [flowEpoch, setFlowEpoch] = useState(0);
369
+ const graphWrapRef = useRef(null);
370
+ const rescueRemountsRef = useRef(0);
371
+ const rescueAttemptsRef = useRef(0);
372
+ const rescueProbeRef = useRef(0);
373
+ const rescueRafRef = useRef(0);
353
374
  const [hasSavedLayout, setHasSavedLayout] = useState(false);
354
375
  const [savedLayoutAt, setSavedLayoutAt] = useState('');
355
376
  const [nodePositions, setNodePositions] = useState({});
@@ -508,6 +529,9 @@ export function KgActiveMemoryExplorer({ title, resetKey, queryMode, items, acti
508
529
  simRef.current = null;
509
530
  pendingViewportRef.current = null;
510
531
  pendingFitViewRef.current = false;
532
+ rescueRemountsRef.current = 0;
533
+ rescueAttemptsRef.current = 0;
534
+ rescueProbeRef.current = 0;
511
535
  const saved = loadSavedLayout(layoutKey);
512
536
  if (saved && Object.keys(saved.positions || {}).length) {
513
537
  setLayoutKind(saved.kind);
@@ -547,7 +571,7 @@ export function KgActiveMemoryExplorer({ title, resetKey, queryMode, items, acti
547
571
  setNodePositions(fallback);
548
572
  // If the user chose a force layout but hasn't saved one, auto-run a short stabilization pass.
549
573
  if (layoutKind === 'force' && graph.nodes.length > 0 && graph.nodes.length <= 320) {
550
- simRef.current = initForceSimulation(graph, { seed, positions: fallback });
574
+ simRef.current = initForceSimulation(graph, { seed, positions: fallback, ...forceOptionsForSpread(layoutSpread) });
551
575
  setLayoutPlaying(true);
552
576
  }
553
577
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -647,6 +671,16 @@ export function KgActiveMemoryExplorer({ title, resetKey, queryMode, items, acti
647
671
  simRef.current = null;
648
672
  }
649
673
  }, [layoutPlaying]);
674
+ useEffect(() => {
675
+ if (layoutKind !== 'force')
676
+ return;
677
+ const sim = simRef.current;
678
+ if (!sim)
679
+ return;
680
+ const opts = forceOptionsForSpread(layoutSpread);
681
+ sim.options.springLength = opts.springLength;
682
+ sim.options.repulsionStrength = opts.repulsionStrength;
683
+ }, [layoutKind, layoutSpread]);
650
684
  useEffect(() => {
651
685
  if (layoutKind === 'force')
652
686
  return;
@@ -663,7 +697,11 @@ export function KgActiveMemoryExplorer({ title, resetKey, queryMode, items, acti
663
697
  if (typeof window === 'undefined' || typeof window.requestAnimationFrame !== 'function')
664
698
  return;
665
699
  if (!simRef.current) {
666
- simRef.current = initForceSimulation(graph, { seed: layoutSeed, positions: nodePositionsRef.current });
700
+ simRef.current = initForceSimulation(graph, {
701
+ seed: layoutSeed,
702
+ positions: nodePositionsRef.current,
703
+ ...forceOptionsForSpread(layoutSpread),
704
+ });
667
705
  }
668
706
  let raf = 0;
669
707
  let lastTs = 0;
@@ -955,10 +993,10 @@ export function KgActiveMemoryExplorer({ title, resetKey, queryMode, items, acti
955
993
  const positions = buildKgLayout(graph, { kind, seed });
956
994
  setNodePositions(positions);
957
995
  if (kind === 'force' && (next.autoPlay ?? true) && graph.nodes.length > 0 && graph.nodes.length <= 320) {
958
- simRef.current = initForceSimulation(graph, { seed, positions });
996
+ simRef.current = initForceSimulation(graph, { seed, positions, ...forceOptionsForSpread(layoutSpread) });
959
997
  setLayoutPlaying(true);
960
998
  }
961
- }, [graph]);
999
+ }, [graph, layoutSpread]);
962
1000
  const toggleSimulation = useCallback(() => {
963
1001
  if (layoutKind !== 'force')
964
1002
  return;
@@ -966,9 +1004,137 @@ export function KgActiveMemoryExplorer({ title, resetKey, queryMode, items, acti
966
1004
  setLayoutPlaying(false);
967
1005
  return;
968
1006
  }
969
- simRef.current = initForceSimulation(graph, { seed: layoutSeed, positions: nodePositionsRef.current });
1007
+ simRef.current = initForceSimulation(graph, {
1008
+ seed: layoutSeed,
1009
+ positions: nodePositionsRef.current,
1010
+ ...forceOptionsForSpread(layoutSpread),
1011
+ });
970
1012
  setLayoutPlaying(true);
971
- }, [graph, layoutKind, layoutPlaying, layoutSeed]);
1013
+ }, [graph, layoutKind, layoutPlaying, layoutSeed, layoutSpread]);
1014
+ useEffect(() => {
1015
+ return () => {
1016
+ if (typeof window === 'undefined')
1017
+ return;
1018
+ if (typeof window.cancelAnimationFrame !== 'function')
1019
+ return;
1020
+ if (rescueRafRef.current)
1021
+ window.cancelAnimationFrame(rescueRafRef.current);
1022
+ };
1023
+ }, []);
1024
+ const isGraphViewportBlank = useCallback(() => {
1025
+ const inst = flowRef.current;
1026
+ if (!inst || typeof inst.getViewport !== 'function')
1027
+ return null;
1028
+ if (!nodeIds.length)
1029
+ return null;
1030
+ const el = graphWrapRef.current;
1031
+ if (!el)
1032
+ return null;
1033
+ const rect = el.getBoundingClientRect();
1034
+ if (!Number.isFinite(rect.width) || !Number.isFinite(rect.height) || rect.width < 64 || rect.height < 64)
1035
+ return null;
1036
+ const vp = inst.getViewport();
1037
+ const zoom = typeof vp?.zoom === 'number' && Number.isFinite(vp.zoom) ? vp.zoom : null;
1038
+ const tx = typeof vp?.x === 'number' && Number.isFinite(vp.x) ? vp.x : null;
1039
+ const ty = typeof vp?.y === 'number' && Number.isFinite(vp.y) ? vp.y : null;
1040
+ if (zoom === null || tx === null || ty === null || zoom <= 0)
1041
+ return null;
1042
+ const margin = 140;
1043
+ const maxCheck = Math.min(nodeIds.length, 180);
1044
+ const pos = nodePositionsRef.current;
1045
+ let checked = 0;
1046
+ let valid = 0;
1047
+ for (const id of nodeIds) {
1048
+ const p = pos[id];
1049
+ if (!p || !Number.isFinite(p.x) || !Number.isFinite(p.y))
1050
+ continue;
1051
+ valid += 1;
1052
+ const candidates = [
1053
+ { x: p.x, y: p.y },
1054
+ { x: p.x + 90, y: p.y + 24 },
1055
+ ];
1056
+ for (const c of candidates) {
1057
+ const sx = c.x * zoom + tx;
1058
+ const sy = c.y * zoom + ty;
1059
+ if (sx > -margin && sx < rect.width + margin && sy > -margin && sy < rect.height + margin)
1060
+ return false;
1061
+ }
1062
+ checked++;
1063
+ if (checked >= maxCheck)
1064
+ break;
1065
+ }
1066
+ if (!valid)
1067
+ return null;
1068
+ return true;
1069
+ }, [nodeIds]);
1070
+ const scheduleViewportRescue = useCallback((why) => {
1071
+ if (typeof window === 'undefined' || typeof window.requestAnimationFrame !== 'function')
1072
+ return;
1073
+ if (rescueRafRef.current)
1074
+ return;
1075
+ rescueRafRef.current = window.requestAnimationFrame(() => {
1076
+ rescueRafRef.current = 0;
1077
+ const blank = isGraphViewportBlank();
1078
+ if (blank === null) {
1079
+ if (rescueProbeRef.current >= 12)
1080
+ return;
1081
+ rescueProbeRef.current += 1;
1082
+ window.setTimeout(() => scheduleViewportRescue(`${why}:probe`), 120);
1083
+ return;
1084
+ }
1085
+ if (blank !== true) {
1086
+ rescueProbeRef.current = 0;
1087
+ rescueAttemptsRef.current = 0;
1088
+ return;
1089
+ }
1090
+ rescueProbeRef.current = 0;
1091
+ const inst = flowRef.current;
1092
+ if (inst && typeof inst.fitView === 'function') {
1093
+ try {
1094
+ inst.fitView({ padding: 0.2, duration: 0 });
1095
+ }
1096
+ catch {
1097
+ try {
1098
+ inst.fitView({ padding: 0.2 });
1099
+ }
1100
+ catch {
1101
+ // ignore
1102
+ }
1103
+ }
1104
+ }
1105
+ rescueAttemptsRef.current += 1;
1106
+ if (rescueAttemptsRef.current >= 4 && rescueRemountsRef.current < 1) {
1107
+ rescueAttemptsRef.current = 0;
1108
+ rescueRemountsRef.current += 1;
1109
+ setFlowEpoch((v) => v + 1);
1110
+ return;
1111
+ }
1112
+ if (rescueAttemptsRef.current < 8) {
1113
+ window.setTimeout(() => scheduleViewportRescue(`${why}:retry`), 220);
1114
+ }
1115
+ });
1116
+ }, [isGraphViewportBlank]);
1117
+ useEffect(() => {
1118
+ rescueAttemptsRef.current = 0;
1119
+ rescueRemountsRef.current = 0;
1120
+ rescueProbeRef.current = 0;
1121
+ scheduleViewportRescue('mount');
1122
+ }, [resetSig, scheduleViewportRescue]);
1123
+ useEffect(() => {
1124
+ scheduleViewportRescue('graph change');
1125
+ }, [nodeIdsSig, scheduleViewportRescue]);
1126
+ useEffect(() => {
1127
+ const el = graphWrapRef.current;
1128
+ if (!el)
1129
+ return;
1130
+ if (typeof window === 'undefined')
1131
+ return;
1132
+ if (typeof ResizeObserver !== 'function')
1133
+ return;
1134
+ const obs = new ResizeObserver(() => scheduleViewportRescue('resize'));
1135
+ obs.observe(el);
1136
+ return () => obs.disconnect();
1137
+ }, [scheduleViewportRescue]);
972
1138
  const saveLayoutNow = useCallback(() => {
973
1139
  const now = new Date().toISOString();
974
1140
  const positions = {};
@@ -1246,77 +1412,79 @@ export function KgActiveMemoryExplorer({ title, resetKey, queryMode, items, acti
1246
1412
  setSelectedNodeId('');
1247
1413
  setSelectedEdgeId('');
1248
1414
  }, []);
1249
- const header = title ? `KG snapshot (${title})` : 'KG snapshot';
1415
+ const header = 'Search KG';
1250
1416
  const nodeCount = graph.nodes.length;
1251
1417
  const edgeCount = graph.edges.length;
1252
1418
  const itemCount = visibleItems.length;
1253
- return (_jsxs("div", { className: "amx-root", children: [_jsxs("div", { className: "amx-left", children: [_jsx("div", { className: "amx-graph", "aria-label": "Knowledge graph", children: _jsx(ReactFlowProvider, { children: _jsxs(ReactFlow, { nodes: nodes, edges: edges, defaultViewport: { x: 0, y: 0, zoom: 1 }, minZoom: AMX_VIEWPORT_MIN_ZOOM, maxZoom: AMX_VIEWPORT_MAX_ZOOM, onInit: (inst) => {
1254
- flowRef.current = inst;
1255
- const vp = pendingViewportRef.current;
1256
- if (vp && typeof inst.setViewport === 'function') {
1257
- try {
1258
- inst.setViewport(vp, { duration: 0 });
1259
- }
1260
- catch {
1419
+ return (_jsxs("div", { className: "amx-root", children: [_jsxs("div", { className: "amx-left", children: [_jsx("div", { className: "amx-graphbar", role: "toolbar", "aria-label": "Graph layout controls", children: _jsxs("div", { className: "amx-graphbar-row", children: [_jsx("span", { className: "amx-graphbar-title", children: "layout" }), _jsxs("select", { value: layoutKind, onChange: (e) => setLayoutKind(normalizeLayoutKind(e.target.value, layoutKind)), disabled: !nodeIds.length, children: [_jsx("option", { value: "grid", children: "grid (deterministic)" }), _jsx("option", { value: "radial", children: "radial (bfs)" }), _jsx("option", { value: "circle", children: "circle" }), _jsx("option", { value: "force", children: "force (simulation)" })] }), layoutKind === 'force' ? (_jsxs("label", { className: "amx-graphbar-spread", children: [_jsx("span", { className: "amx-graphbar-title", children: "spread" }), _jsx("input", { type: "range", min: "0.7", max: "2.2", step: "0.1", value: layoutSpread, onChange: (e) => setLayoutSpread(clampNumber(Number(e.target.value), 0.7, 2.2, 1)) }), _jsxs("span", { className: "amx-small", children: [layoutSpread.toFixed(1), "\u00D7"] })] })) : null, _jsx("button", { type: "button", className: "amx-btn", onClick: () => applyLayoutNow({ kind: layoutKind, seed: layoutSeed }), disabled: !nodeIds.length, children: "Apply" }), _jsx("button", { type: "button", className: "amx-btn", onClick: toggleSimulation, disabled: !nodeIds.length || layoutKind !== 'force', children: layoutPlaying ? 'Pause simulation' : 'Play simulation' })] }) }), _jsxs("div", { className: "amx-graph", "aria-label": "Knowledge graph", ref: graphWrapRef, children: [_jsx(ReactFlowProvider, { children: _jsxs(ReactFlow, { nodes: nodes, edges: edges, defaultViewport: { x: 0, y: 0, zoom: 1 }, minZoom: AMX_VIEWPORT_MIN_ZOOM, maxZoom: AMX_VIEWPORT_MAX_ZOOM, onInit: (inst) => {
1420
+ flowRef.current = inst;
1421
+ const vp = pendingViewportRef.current;
1422
+ if (vp && typeof inst.setViewport === 'function') {
1261
1423
  try {
1262
- inst.setViewport(vp);
1424
+ inst.setViewport(vp, { duration: 0 });
1263
1425
  }
1264
1426
  catch {
1265
- // ignore
1427
+ try {
1428
+ inst.setViewport(vp);
1429
+ }
1430
+ catch {
1431
+ // ignore
1432
+ }
1266
1433
  }
1267
1434
  }
1268
- }
1269
- if (pendingFitViewRef.current && typeof inst.fitView === 'function') {
1270
- try {
1271
- inst.fitView({ padding: 0.2, duration: 0 });
1272
- }
1273
- catch {
1435
+ if (pendingFitViewRef.current && typeof inst.fitView === 'function') {
1274
1436
  try {
1275
- inst.fitView({ padding: 0.2 });
1437
+ inst.fitView({ padding: 0.2, duration: 0 });
1276
1438
  }
1277
1439
  catch {
1278
- // ignore
1440
+ try {
1441
+ inst.fitView({ padding: 0.2 });
1442
+ }
1443
+ catch {
1444
+ // ignore
1445
+ }
1279
1446
  }
1280
1447
  }
1281
- }
1282
- pendingViewportRef.current = null;
1283
- pendingFitViewRef.current = false;
1284
- }, nodesDraggable: true, nodesConnectable: false, elementsSelectable: true, onNodesChange: onNodesChange, onNodeClick: (_, n) => {
1285
- setSelectedEdgeId('');
1286
- setSelectedNodeId(n.id);
1287
- }, onEdgeClick: (_, e) => {
1288
- setSelectedNodeId('');
1289
- setSelectedEdgeId(e.id);
1290
- }, onPaneClick: () => {
1291
- setSelectedNodeId('');
1292
- setSelectedEdgeId('');
1293
- }, children: [_jsx(Controls, {}), _jsx(Panel, { position: "top-right", children: _jsxs("div", { className: "amx-panel-toggles", children: [_jsx("button", { type: "button", className: "amx-minimap-toggle", onClick: () => setShowMiniMap((v) => !v), title: showMiniMap ? 'Hide minimap preview' : 'Show minimap preview', "aria-label": showMiniMap ? 'Hide minimap preview' : 'Show minimap preview', children: showMiniMap ? 'minimap on' : 'minimap off' }), layoutKind === 'force' ? (_jsx("button", { type: "button", className: "amx-minimap-toggle", onClick: toggleSimulation, title: layoutPlaying ? 'Pause force layout simulation' : 'Play force layout simulation', "aria-label": layoutPlaying ? 'Pause simulation' : 'Play simulation', children: layoutPlaying ? 'sim pause' : 'sim play' })) : null] }) }), showMiniMap ? (_jsx(MiniMap, { pannable: true, zoomable: true, maskColor: "rgba(0,0,0,0.45)", style: {
1294
- background: 'rgba(12, 18, 34, 0.88)',
1295
- border: '1px solid rgba(255,255,255,0.12)',
1296
- borderRadius: 10,
1297
- width: compactViewport ? 120 : 160,
1298
- height: compactViewport ? 80 : 110,
1299
- }, nodeColor: (n) => kindColor(n.data?.kind).minimap })) : null, _jsx(Background, { gap: 20, size: 1, color: "rgba(255,255,255,0.06)" })] }) }) }), _jsxs("details", { className: "amx-panel amx-controls", children: [_jsxs("summary", { children: [_jsx("span", { className: "amx-controls-title", children: header }), _jsxs("span", { className: "amx-small", children: [itemCount, " assertions \u00B7 ", nodeCount, " nodes \u00B7 ", edgeCount, " edges"] })] }), _jsxs("div", { className: "amx-toolbar", style: { marginTop: 10 }, children: [_jsxs("div", { className: "amx-toolbar-grid", children: [_jsxs("label", { children: ["search / highlight", _jsx("input", { value: search, onChange: (e) => setSearch(e.target.value), placeholder: "filter nodes by id/label" })] }), _jsxs("label", { children: ["group edges", _jsxs("select", { value: String(groupEdges), onChange: (e) => setGroupEdges(e.target.value === 'true'), children: [_jsx("option", { value: "true", children: "group (subject\u2192object)" }), _jsx("option", { value: "false", children: "no grouping" })] })] }), _jsxs("label", { children: ["view", _jsxs("select", { value: String(showStructural), onChange: (e) => setShowStructural(e.target.value === 'true'), children: [_jsx("option", { value: "true", children: "all assertions" }), _jsx("option", { value: "false", children: "hide rdf:type / labels" })] })] }), _jsxs("label", { children: ["path mode", _jsxs("select", { value: String(directedPath), onChange: (e) => setDirectedPath(e.target.value === 'true'), children: [_jsx("option", { value: "false", children: "undirected" }), _jsx("option", { value: "true", children: "directed" })] })] })] }), _jsxs("details", { className: "amx-details", children: [_jsx("summary", { children: "layout" }), _jsxs("div", { className: "amx-toolbar-grid", style: { marginTop: 10 }, children: [_jsxs("label", { children: ["layout", _jsxs("select", { value: layoutKind, onChange: (e) => setLayoutKind(normalizeLayoutKind(e.target.value, layoutKind)), children: [_jsx("option", { value: "grid", children: "grid (deterministic)" }), _jsx("option", { value: "radial", children: "radial (bfs)" }), _jsx("option", { value: "circle", children: "circle" }), _jsx("option", { value: "force", children: "force (simulation)" })] })] }), _jsxs("label", { children: ["seed", _jsx("input", { type: "number", step: "1", value: layoutSeed, onChange: (e) => setLayoutSeed(Math.trunc(Number(e.target.value || 0) || 0)) })] })] }), _jsxs("div", { className: "amx-actions", style: { marginTop: 10 }, children: [_jsx("button", { type: "button", className: "amx-btn", onClick: () => applyLayoutNow({ kind: layoutKind, seed: layoutSeed }), disabled: !nodeIds.length, children: "Apply layout" }), layoutKind === 'force' ? (_jsx("button", { type: "button", className: "amx-btn", onClick: toggleSimulation, disabled: !nodeIds.length, children: layoutPlaying ? 'Pause simulation' : 'Play simulation' })) : null, _jsx("button", { type: "button", className: "amx-btn", onClick: saveLayoutNow, disabled: !nodeIds.length, children: "Save layout" }), hasSavedLayout ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "amx-btn", onClick: loadSavedLayoutNow, children: "Load saved" }), _jsx("button", { type: "button", className: "amx-btn", onClick: clearSavedLayoutNow, children: "Clear saved" })] })) : null] }), _jsxs("div", { className: "amx-small", style: { marginTop: 10, opacity: 0.9 }, children: ["Drag nodes to adjust manually; use ", _jsx("span", { className: "amx-mono", children: "Save layout" }), " for a stable, replayable view.", hasSavedLayout ? (_jsxs("span", { children: [' ', "(saved", savedLayoutAt ? `: ${savedLayoutAt}` : '', ")"] })) : (_jsx("span", { children: " (no saved layout)" }))] })] }), _jsxs("details", { className: "amx-details", children: [_jsx("summary", { children: "path" }), _jsxs("div", { className: "amx-toolbar-grid", style: { marginTop: 10 }, children: [_jsxs("label", { children: ["path start", _jsxs("select", { value: pathStart, onChange: (e) => setPathStart(e.target.value), children: [_jsx("option", { value: "", children: "(none)" }), nodeIds.map((id) => (_jsx("option", { value: id, children: (() => {
1300
- const label = nodeLabelById.get(id) || id;
1301
- return label === id ? label : `${label} (${id})`;
1302
- })() }, id)))] })] }), _jsxs("label", { children: ["path end", _jsxs("select", { value: pathEnd, onChange: (e) => setPathEnd(e.target.value), children: [_jsx("option", { value: "", children: "(none)" }), nodeIds.map((id) => (_jsx("option", { value: id, children: (() => {
1303
- const label = nodeLabelById.get(id) || id;
1304
- return label === id ? label : `${label} (${id})`;
1305
- })() }, id)))] })] })] }), path ? (_jsx("div", { className: "amx-small", style: { marginTop: 10 }, children: pathSegments.map((seg, idx) => {
1306
- const fromLabel = nodeLabelById.get(seg.from) || seg.from;
1307
- const toLabel = nodeLabelById.get(seg.to) || seg.to;
1308
- const arrow = seg.dir === 'reverse' ? ' ' : ' ';
1309
- const pred = seg.predicate ? ` (${seg.predicate})` : '';
1310
- return (_jsxs("div", { children: [idx === 0 ? _jsx("span", { children: fromLabel }) : null, arrow, _jsxs("span", { children: [toLabel, pred] })] }, `${seg.edgeId}:${idx}`));
1311
- }) })) : pathStart && pathEnd ? (_jsxs("div", { className: "amx-small", style: { marginTop: 10, opacity: 0.9 }, children: ["No path found in the current subgraph (", noPathDiagnostics?.assertions ?? itemCount, " assertions \u00B7 ", noPathDiagnostics?.nodes ?? graph.nodes.length, " nodes \u00B7", ' ', noPathDiagnostics?.edges ?? graph.edges.length, " edges). Start reaches ", noPathDiagnostics?.reachableFromStart ?? 0, " nodes.", onQuery ? (_jsx("div", { style: { marginTop: 10 }, children: _jsx("button", { type: "button", className: "amx-btn", onClick: () => void expandNeighborhoodForPath(), disabled: expandLoading, children: expandLoading ? 'Expanding…' : 'Expand neighborhood' }) })) : null] })) : (_jsx("div", { className: "amx-small", style: { marginTop: 10 }, children: "(no path)" }))] }), _jsxs("details", { className: "amx-details", children: [_jsx("summary", { children: "query" }), _jsxs("div", { className: "amx-toolbar-grid", style: { marginTop: 10 }, children: [_jsxs("label", { children: ["query_text (semantic)", _jsx("input", { value: queryText, onChange: (e) => setQueryText(e.target.value), placeholder: "e.g. emotion chip" })] }), _jsxs("label", { children: ["subject (exact)", _jsx("input", { value: subject, onChange: (e) => setSubject(e.target.value), placeholder: "e.g. ex:person-data" })] }), _jsxs("label", { children: ["predicate (exact)", _jsx("input", { value: predicate, onChange: (e) => setPredicate(e.target.value), placeholder: "e.g. schema:about" })] }), _jsxs("label", { children: ["object (exact)", _jsx("input", { value: object, onChange: (e) => setObject(e.target.value), placeholder: "e.g. ex:concept-emotion-chip" })] }), _jsxs("label", { children: ["min_score", _jsx("input", { type: "number", step: "any", value: minScore, onChange: (e) => setMinScore(e.target.value), placeholder: "(auto)" })] }), _jsxs("label", { children: ["limit", _jsx("input", { type: "number", min: "-1", step: "1", value: limit, onChange: (e) => setLimit(e.target.value), placeholder: "0/-1 = unlimited (if supported)" })] })] })] }), _jsxs("details", { className: "amx-details", children: [_jsx("summary", { children: "advanced" }), _jsxs("div", { className: "amx-toolbar-grid", style: { marginTop: 10 }, children: [_jsxs("label", { children: ["scope", _jsxs("select", { value: scope, onChange: (e) => setScope(normalizeScope(e.target.value, scope)), children: [_jsx("option", { value: "run", children: "run" }), _jsx("option", { value: "session", children: "session" }), _jsx("option", { value: "global", children: "global" }), _jsx("option", { value: "all", children: "all" })] })] }), _jsxs("label", { children: ["recall level", _jsxs("select", { value: recallLevel, onChange: (e) => setRecallLevel(e.target.value || 'standard'), children: [_jsx("option", { value: "urgent", children: "urgent" }), _jsx("option", { value: "standard", children: "standard" }), _jsx("option", { value: "deep", children: "deep" })] })] }), _jsxs("label", { children: ["max_input_tokens", _jsx("input", { type: "number", min: "0", step: "1", value: maxInputTokens, onChange: (e) => setMaxInputTokens(e.target.value), placeholder: "(auto)" })] }), _jsxs("label", { children: ["model (budgeting)", _jsx("input", { value: model, onChange: (e) => setModel(e.target.value), placeholder: "qwen/qwen3-next-80b" })] })] })] }), _jsxs("div", { className: "amx-actions", children: [_jsx("button", { type: "button", className: "amx-btn", onClick: () => void runQuery(), disabled: !onQuery || queryLoading, children: queryLoading ? 'Querying…' : 'Query store' }), _jsx("button", { type: "button", className: "amx-btn", onClick: resetToStep, disabled: !override && !queryError && !queryLoading, children: "Reset to step output" }), override ? (_jsxs("span", { className: "amx-small", children: ["showing: ", overrideKind || 'live query'] })) : (_jsx("span", { className: "amx-small", children: "showing: step output" })), queryError ? _jsx("span", { className: "amx-small", style: { color: 'rgba(255, 80, 80, 0.95)' }, children: queryError }) : null, expandError ? _jsx("span", { className: "amx-small", style: { color: 'rgba(255, 80, 80, 0.95)' }, children: expandError }) : null] }), _jsxs("details", { className: "amx-details", children: [_jsx("summary", { children: "node colors" }), _jsx("div", { className: "amx-legend", style: { marginTop: 10 }, children: [
1312
- { k: 'person', label: 'person' },
1313
- { k: 'org', label: 'org' },
1314
- { k: 'concept', label: 'concept' },
1315
- { k: 'claim', label: 'claim' },
1316
- { k: 'event', label: 'event' },
1317
- { k: 'doc', label: 'doc' },
1318
- { k: 'vocab', label: 'vocab' },
1319
- ].map(({ k, label }) => (_jsx("span", { className: "amx-legend-item", style: { borderColor: kindColor(k).stroke }, children: label }, k))) })] })] })] })] }), _jsxs("div", { className: "amx-right", children: [_jsxs("div", { className: "amx-panel", children: [_jsx("h3", { children: "Details" }), _jsx("div", { className: "amx-small", style: { marginBottom: 8 }, children: "Derived from `memory_kg_query` packetization (max_input_tokens); safe to inject into an LLM system prompt." }), (() => {
1448
+ pendingViewportRef.current = null;
1449
+ pendingFitViewRef.current = false;
1450
+ scheduleViewportRescue('init');
1451
+ }, nodesDraggable: true, nodesConnectable: false, elementsSelectable: true, onNodesChange: onNodesChange, onNodeClick: (_, n) => {
1452
+ setSelectedEdgeId('');
1453
+ setSelectedNodeId(n.id);
1454
+ }, onEdgeClick: (_, e) => {
1455
+ setSelectedNodeId('');
1456
+ setSelectedEdgeId(e.id);
1457
+ }, onPaneClick: () => {
1458
+ setSelectedNodeId('');
1459
+ setSelectedEdgeId('');
1460
+ }, children: [_jsx(Controls, { className: "amx-flow-controls", children: _jsx(ControlButton, { type: "button", className: `amx-control-legend ${showLegend ? 'amx-control-legend-active' : ''}`, onClick: () => setShowLegend((v) => !v), title: showLegend ? 'Hide node color legend' : 'Show node color legend', "aria-label": showLegend ? 'Hide node color legend' : 'Show node color legend', children: _jsx(LegendIcon, {}) }) }), _jsx(Panel, { position: "bottom-right", className: "amx-panel-bottom-right", children: _jsx("div", { className: "amx-panel-toggles", children: _jsx("button", { type: "button", className: "amx-minimap-toggle", onClick: () => setShowMiniMap((v) => !v), title: showMiniMap ? 'Hide minimap preview' : 'Show minimap preview', "aria-label": showMiniMap ? 'Hide minimap preview' : 'Show minimap preview', children: showMiniMap ? 'minimap on' : 'minimap off' }) }) }), showMiniMap ? (_jsx(MiniMap, { position: "bottom-right", pannable: true, zoomable: true, maskColor: "rgba(0,0,0,0.45)", style: {
1461
+ background: 'rgba(12, 18, 34, 0.88)',
1462
+ border: '1px solid rgba(255,255,255,0.12)',
1463
+ borderRadius: 10,
1464
+ width: compactViewport ? 120 : 160,
1465
+ height: compactViewport ? 80 : 110,
1466
+ marginBottom: 56,
1467
+ }, nodeColor: (n) => kindColor(n.data?.kind).minimap })) : null, showLegend ? (_jsxs(Panel, { position: "bottom-left", className: "amx-legend-panel", children: [_jsx("div", { className: "amx-legend-title", children: "node colors" }), _jsx("div", { className: "amx-legend", children: [
1468
+ { k: 'person', label: 'person' },
1469
+ { k: 'org', label: 'org' },
1470
+ { k: 'concept', label: 'concept' },
1471
+ { k: 'claim', label: 'claim' },
1472
+ { k: 'event', label: 'event' },
1473
+ { k: 'doc', label: 'doc' },
1474
+ { k: 'vocab', label: 'vocab' },
1475
+ ].map(({ k, label }) => (_jsx("span", { className: "amx-legend-item", style: { borderColor: kindColor(k).stroke }, children: label }, k))) })] })) : null, _jsx(Background, { gap: 20, size: 1, color: "rgba(255,255,255,0.06)" })] }) }, flowEpoch), _jsxs("details", { className: "amx-panel amx-controls", children: [_jsxs("summary", { children: [_jsx("span", { className: "amx-controls-title", children: header }), _jsxs("span", { className: "amx-small", children: [itemCount, " assertions \u00B7 ", nodeCount, " nodes \u00B7 ", edgeCount, " edges"] })] }), _jsxs("div", { className: "amx-toolbar", style: { marginTop: 10 }, children: [_jsxs("div", { className: "amx-toolbar-grid", children: [_jsxs("label", { children: ["search / highlight", _jsx("input", { value: search, onChange: (e) => setSearch(e.target.value), placeholder: "filter nodes by id/label" })] }), _jsxs("label", { children: ["group edges", _jsxs("select", { value: String(groupEdges), onChange: (e) => setGroupEdges(e.target.value === 'true'), children: [_jsx("option", { value: "true", children: "group (subject\u2192object)" }), _jsx("option", { value: "false", children: "no grouping" })] })] }), _jsxs("label", { children: ["view", _jsxs("select", { value: String(showStructural), onChange: (e) => setShowStructural(e.target.value === 'true'), children: [_jsx("option", { value: "true", children: "all assertions" }), _jsx("option", { value: "false", children: "hide rdf:type / labels" })] })] }), _jsxs("label", { children: ["path mode", _jsxs("select", { value: String(directedPath), onChange: (e) => setDirectedPath(e.target.value === 'true'), children: [_jsx("option", { value: "false", children: "undirected" }), _jsx("option", { value: "true", children: "directed" })] })] })] }), _jsxs("details", { className: "amx-details", children: [_jsx("summary", { children: "layout" }), _jsxs("div", { className: "amx-toolbar-grid", style: { marginTop: 10 }, children: [_jsxs("label", { children: ["layout", _jsxs("select", { value: layoutKind, onChange: (e) => setLayoutKind(normalizeLayoutKind(e.target.value, layoutKind)), children: [_jsx("option", { value: "grid", children: "grid (deterministic)" }), _jsx("option", { value: "radial", children: "radial (bfs)" }), _jsx("option", { value: "circle", children: "circle" }), _jsx("option", { value: "force", children: "force (simulation)" })] })] }), _jsxs("label", { children: ["seed", _jsx("input", { type: "number", step: "1", value: layoutSeed, onChange: (e) => setLayoutSeed(Math.trunc(Number(e.target.value || 0) || 0)) })] })] }), _jsxs("div", { className: "amx-actions", style: { marginTop: 10 }, children: [_jsx("button", { type: "button", className: "amx-btn", onClick: () => applyLayoutNow({ kind: layoutKind, seed: layoutSeed }), disabled: !nodeIds.length, children: "Apply layout" }), layoutKind === 'force' ? (_jsx("button", { type: "button", className: "amx-btn", onClick: toggleSimulation, disabled: !nodeIds.length, children: layoutPlaying ? 'Pause simulation' : 'Play simulation' })) : null, _jsx("button", { type: "button", className: "amx-btn", onClick: saveLayoutNow, disabled: !nodeIds.length, children: "Save layout" }), hasSavedLayout ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "amx-btn", onClick: loadSavedLayoutNow, children: "Load saved" }), _jsx("button", { type: "button", className: "amx-btn", onClick: clearSavedLayoutNow, children: "Clear saved" })] })) : null] }), _jsxs("div", { className: "amx-small", style: { marginTop: 10, opacity: 0.9 }, children: ["Drag nodes to adjust manually; use ", _jsx("span", { className: "amx-mono", children: "Save layout" }), " for a stable, replayable view.", hasSavedLayout ? (_jsxs("span", { children: [' ', "(saved", savedLayoutAt ? `: ${savedLayoutAt}` : '', ")"] })) : (_jsx("span", { children: " (no saved layout)" }))] })] }), _jsxs("details", { className: "amx-details", children: [_jsx("summary", { children: "path" }), _jsxs("div", { className: "amx-toolbar-grid", style: { marginTop: 10 }, children: [_jsxs("label", { children: ["path start", _jsxs("select", { value: pathStart, onChange: (e) => setPathStart(e.target.value), children: [_jsx("option", { value: "", children: "(none)" }), nodeIds.map((id) => (_jsx("option", { value: id, children: (() => {
1476
+ const label = nodeLabelById.get(id) || id;
1477
+ return label === id ? label : `${label} (${id})`;
1478
+ })() }, id)))] })] }), _jsxs("label", { children: ["path end", _jsxs("select", { value: pathEnd, onChange: (e) => setPathEnd(e.target.value), children: [_jsx("option", { value: "", children: "(none)" }), nodeIds.map((id) => (_jsx("option", { value: id, children: (() => {
1479
+ const label = nodeLabelById.get(id) || id;
1480
+ return label === id ? label : `${label} (${id})`;
1481
+ })() }, id)))] })] })] }), path ? (_jsx("div", { className: "amx-small", style: { marginTop: 10 }, children: pathSegments.map((seg, idx) => {
1482
+ const fromLabel = nodeLabelById.get(seg.from) || seg.from;
1483
+ const toLabel = nodeLabelById.get(seg.to) || seg.to;
1484
+ const arrow = seg.dir === 'reverse' ? ' ← ' : '';
1485
+ const pred = seg.predicate ? ` (${seg.predicate})` : '';
1486
+ return (_jsxs("div", { children: [idx === 0 ? _jsx("span", { children: fromLabel }) : null, arrow, _jsxs("span", { children: [toLabel, pred] })] }, `${seg.edgeId}:${idx}`));
1487
+ }) })) : pathStart && pathEnd ? (_jsxs("div", { className: "amx-small", style: { marginTop: 10, opacity: 0.9 }, children: ["No path found in the current subgraph (", noPathDiagnostics?.assertions ?? itemCount, " assertions \u00B7 ", noPathDiagnostics?.nodes ?? graph.nodes.length, " nodes \u00B7", ' ', noPathDiagnostics?.edges ?? graph.edges.length, " edges). Start reaches ", noPathDiagnostics?.reachableFromStart ?? 0, " nodes.", onQuery ? (_jsx("div", { style: { marginTop: 10 }, children: _jsx("button", { type: "button", className: "amx-btn", onClick: () => void expandNeighborhoodForPath(), disabled: expandLoading, children: expandLoading ? 'Expanding…' : 'Expand neighborhood' }) })) : null] })) : (_jsx("div", { className: "amx-small", style: { marginTop: 10 }, children: "(no path)" }))] }), _jsxs("details", { className: "amx-details", children: [_jsx("summary", { children: "query" }), _jsxs("div", { className: "amx-toolbar-grid", style: { marginTop: 10 }, children: [_jsxs("label", { children: ["query_text (semantic)", _jsx("input", { value: queryText, onChange: (e) => setQueryText(e.target.value), placeholder: "e.g. emotion chip" })] }), _jsxs("label", { children: ["subject (exact)", _jsx("input", { value: subject, onChange: (e) => setSubject(e.target.value), placeholder: "e.g. ex:person-data" })] }), _jsxs("label", { children: ["predicate (exact)", _jsx("input", { value: predicate, onChange: (e) => setPredicate(e.target.value), placeholder: "e.g. schema:about" })] }), _jsxs("label", { children: ["object (exact)", _jsx("input", { value: object, onChange: (e) => setObject(e.target.value), placeholder: "e.g. ex:concept-emotion-chip" })] }), _jsxs("label", { children: ["min_score", _jsx("input", { type: "number", step: "any", value: minScore, onChange: (e) => setMinScore(e.target.value), placeholder: "(auto)" })] }), _jsxs("label", { children: ["limit", _jsx("input", { type: "number", min: "-1", step: "1", value: limit, onChange: (e) => setLimit(e.target.value), placeholder: "0/-1 = unlimited (if supported)" })] })] })] }), _jsxs("details", { className: "amx-details", children: [_jsx("summary", { children: "advanced" }), _jsxs("div", { className: "amx-toolbar-grid", style: { marginTop: 10 }, children: [_jsxs("label", { children: ["scope", _jsxs("select", { value: scope, onChange: (e) => setScope(normalizeScope(e.target.value, scope)), children: [_jsx("option", { value: "run", children: "run" }), _jsx("option", { value: "session", children: "session" }), _jsx("option", { value: "global", children: "global" }), _jsx("option", { value: "all", children: "all" })] })] }), _jsxs("label", { children: ["recall level", _jsxs("select", { value: recallLevel, onChange: (e) => setRecallLevel(e.target.value || 'standard'), children: [_jsx("option", { value: "urgent", children: "urgent" }), _jsx("option", { value: "standard", children: "standard" }), _jsx("option", { value: "deep", children: "deep" })] })] }), _jsxs("label", { children: ["max_input_tokens", _jsx("input", { type: "number", min: "0", step: "1", value: maxInputTokens, onChange: (e) => setMaxInputTokens(e.target.value), placeholder: "(auto)" })] }), _jsxs("label", { children: ["model (budgeting)", _jsx("input", { value: model, onChange: (e) => setModel(e.target.value), placeholder: "qwen/qwen3-next-80b" })] })] })] }), _jsxs("div", { className: "amx-actions", children: [_jsx("button", { type: "button", className: "amx-btn", onClick: () => void runQuery(), disabled: !onQuery || queryLoading, children: queryLoading ? 'Querying…' : 'Query store' }), _jsx("button", { type: "button", className: "amx-btn", onClick: resetToStep, disabled: !override && !queryError && !queryLoading, children: "Reset to step output" }), override ? (_jsxs("span", { className: "amx-small", children: ["showing: ", overrideKind || 'live query'] })) : (_jsx("span", { className: "amx-small", children: "showing: step output" })), queryError ? _jsx("span", { className: "amx-small", style: { color: 'rgba(255, 80, 80, 0.95)' }, children: queryError }) : null, expandError ? _jsx("span", { className: "amx-small", style: { color: 'rgba(255, 80, 80, 0.95)' }, children: expandError }) : null] })] })] })] })] }), _jsxs("div", { className: "amx-right", children: [_jsxs("div", { className: "amx-panel", children: [_jsx("h3", { children: "Details" }), _jsx("div", { className: "amx-small", style: { marginBottom: 8 }, children: "Derived from `memory_kg_query` packetization (max_input_tokens); safe to inject into an LLM system prompt." }), (() => {
1320
1488
  const s = formatEffort(displayEffort);
1321
1489
  if (!s)
1322
1490
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abstractframework/monitor-active-memory",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "description": "Shared KG + Active Memory explorer UI components for AbstractFramework clients (AbstractFlow, AbstractObserver, AbstractCode).",
5
5
  "type": "module",
6
6
  "author": "Laurent-Philippe Albou",
@@ -45,6 +45,9 @@
45
45
  "reactflow": "^11.0.0"
46
46
  },
47
47
  "devDependencies": {
48
+ "react": "^18.0.0",
49
+ "react-dom": "^18.0.0",
50
+ "reactflow": "^11.0.0",
48
51
  "@types/react": "^18.2.48",
49
52
  "@types/react-dom": "^18.2.18",
50
53
  "typescript": "^5.3.3"
package/src/styles.css CHANGED
@@ -97,11 +97,53 @@
97
97
  .amx-graph {
98
98
  flex: 1 1 auto;
99
99
  min-height: 420px;
100
+ position: relative;
100
101
  border: 1px solid var(--ui-border-1, rgba(255, 255, 255, 0.08));
101
102
  border-radius: 10px;
102
103
  overflow: hidden;
103
104
  }
104
105
 
106
+ .amx-graphbar {
107
+ display: flex;
108
+ align-items: center;
109
+ justify-content: space-between;
110
+ gap: 10px;
111
+ padding: 10px;
112
+ border: 1px solid var(--ui-border-1, rgba(255, 255, 255, 0.08));
113
+ border-radius: 10px;
114
+ background: var(--ui-surface-2, rgba(255, 255, 255, 0.02));
115
+ }
116
+
117
+ .amx-graphbar-row {
118
+ display: flex;
119
+ align-items: center;
120
+ gap: 10px;
121
+ flex-wrap: wrap;
122
+ }
123
+
124
+ .amx-graphbar-title {
125
+ font-size: var(--font-size-sm, 12px);
126
+ opacity: 0.9;
127
+ }
128
+
129
+ .amx-graphbar select {
130
+ padding: 8px 10px;
131
+ border-radius: 8px;
132
+ border: 1px solid var(--ui-border-1, rgba(255, 255, 255, 0.10));
133
+ background: var(--ui-surface-3, rgba(0, 0, 0, 0.2));
134
+ color: inherit;
135
+ }
136
+
137
+ .amx-graphbar-spread {
138
+ display: inline-flex;
139
+ align-items: center;
140
+ gap: 8px;
141
+ }
142
+
143
+ .amx-graphbar-spread input[type='range'] {
144
+ width: 140px;
145
+ }
146
+
105
147
  .amx-right {
106
148
  flex: 0 0 380px;
107
149
  width: 380px;
@@ -292,9 +334,24 @@
292
334
  border: 1px solid var(--ui-border-1, rgba(255, 255, 255, 0.12));
293
335
  border-radius: 10px;
294
336
  box-shadow: var(--ui-shadow-1, 0 14px 38px rgba(0, 0, 0, 0.45));
337
+ display: flex;
338
+ flex-direction: column;
295
339
  overflow: hidden;
296
340
  }
297
341
 
342
+ .amx-graph .react-flow__controls .amx-control-legend {
343
+ order: 4;
344
+ }
345
+
346
+ .amx-graph .react-flow__controls .amx-control-legend-active {
347
+ background: var(--accent-subtle, rgba(233, 69, 96, 0.25));
348
+ color: var(--text-primary, rgba(255, 255, 255, 0.96));
349
+ }
350
+
351
+ .amx-graph .react-flow__controls .react-flow__controls-interactive {
352
+ order: 5;
353
+ }
354
+
298
355
  .amx-graph .react-flow__controls button {
299
356
  background: transparent;
300
357
  border: none;
@@ -309,7 +366,7 @@
309
366
  color: var(--text-primary, rgba(255, 255, 255, 0.96));
310
367
  }
311
368
 
312
- .amx-graph .react-flow__controls button:last-child {
369
+ .amx-graph .react-flow__controls .react-flow__controls-interactive {
313
370
  border-bottom: none;
314
371
  }
315
372
 
@@ -347,6 +404,23 @@
347
404
  align-items: flex-end;
348
405
  }
349
406
 
407
+ .amx-legend-panel {
408
+ background: var(--ui-code-block-bg, rgba(12, 18, 34, 0.78));
409
+ border: 1px solid var(--ui-border-1, rgba(255, 255, 255, 0.12));
410
+ border-radius: 10px;
411
+ padding: 10px;
412
+ box-shadow: var(--ui-shadow-1, 0 14px 38px rgba(0, 0, 0, 0.45));
413
+ backdrop-filter: blur(10px);
414
+ margin-left: 52px;
415
+ margin-bottom: 56px;
416
+ }
417
+
418
+ .amx-legend-title {
419
+ font-size: var(--font-size-sm, 12px);
420
+ opacity: 0.9;
421
+ margin-bottom: 8px;
422
+ }
423
+
350
424
  .amx-graph .react-flow__minimap {
351
425
  border-radius: 10px;
352
426
  overflow: hidden;