@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 +13 -7
- package/dist/KgActiveMemoryExplorer.d.ts +1 -2
- package/dist/KgActiveMemoryExplorer.d.ts.map +1 -1
- package/dist/KgActiveMemoryExplorer.js +236 -68
- package/package.json +4 -1
- package/src/styles.css +75 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @
|
|
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 `@
|
|
24
|
-
- npm (once published): `npm i @
|
|
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 "@
|
|
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
|
-
-
|
|
65
|
+
- Import CSS in your app entrypoint (recommended):
|
|
66
66
|
|
|
67
67
|
```ts
|
|
68
|
-
import "
|
|
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
|
-
-
|
|
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({
|
|
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":"
|
|
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,
|
|
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({
|
|
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, {
|
|
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, {
|
|
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 =
|
|
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
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1440
|
+
try {
|
|
1441
|
+
inst.fitView({ padding: 0.2 });
|
|
1442
|
+
}
|
|
1443
|
+
catch {
|
|
1444
|
+
// ignore
|
|
1445
|
+
}
|
|
1279
1446
|
}
|
|
1280
1447
|
}
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
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.
|
|
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
|
|
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;
|