pi-mega-compact 0.14.1 → 0.15.1
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/dist/extensions/dashboard-server/api-contracts/endpoints/registry.js +6 -0
- package/dist/extensions/dashboard-server/resolve-snapshot.js +20 -8
- package/dist/extensions/dashboard-server/routes-game.js +60 -0
- package/dist/extensions/dashboard-server/routes.js +1 -1
- package/dist/extensions/dashboard-server/server.js +3 -1
- package/extensions/dashboard-client/dist/assets/AreaChart-rJdaOUlj.js +2 -0
- package/extensions/dashboard-client/dist/assets/AreaChart-rJdaOUlj.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/BarChart-DOfc8me9.js +2 -0
- package/extensions/dashboard-client/dist/assets/BarChart-DOfc8me9.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{CacheTab-VNFZSdRr.js → CacheTab-EhqkEglo.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-VNFZSdRr.js.map → CacheTab-EhqkEglo.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-Dr7RDBy2.js → EventsTab-BUACwWXf.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-Dr7RDBy2.js.map → EventsTab-BUACwWXf.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-B8VZ8gbb.js → HealthTab-CDEYVRBb.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-B8VZ8gbb.js.map → HealthTab-CDEYVRBb.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DMtrXRcn.js → MaintenanceTab-DtanIeCQ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DMtrXRcn.js.map → MaintenanceTab-DtanIeCQ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-PFrMdLOm.js → MemoryMapTab-BGEhL7OG.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-PFrMdLOm.js.map → MemoryMapTab-BGEhL7OG.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/MetricsTab-Cktozn4s.js +2 -0
- package/extensions/dashboard-client/dist/assets/MetricsTab-Cktozn4s.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/OverviewTab-08v6Ayuf.js +16 -0
- package/extensions/dashboard-client/dist/assets/OverviewTab-08v6Ayuf.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{ReposTab-D6pEUtEJ.js → ReposTab-Bg72Ozdc.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-D6pEUtEJ.js.map → ReposTab-Bg72Ozdc.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/SessionsTab-BcTK9Lg6.js +2 -0
- package/extensions/dashboard-client/dist/assets/{SessionsTab-CcFzDJ7W.js.map → SessionsTab-BcTK9Lg6.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-YCt5NGB5.js → SetupTab-Bt-Ru1xp.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-YCt5NGB5.js.map → SetupTab-Bt-Ru1xp.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BsLNhOOj.js → TimeSavedCard-ic_LjJ79.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BsLNhOOj.js.map → TimeSavedCard-ic_LjJ79.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-C1jpVZrH.js → TurnsTab-D9AZTMgG.js} +4 -4
- package/extensions/dashboard-client/dist/assets/{TurnsTab-C1jpVZrH.js.map → TurnsTab-D9AZTMgG.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/WikiTab-hRcJPi_m.js +2 -0
- package/extensions/dashboard-client/dist/assets/{WikiTab-RHoVt95g.js.map → WikiTab-hRcJPi_m.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{button-DSZI4aZ5.js → button-BdAtIbVu.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{button-DSZI4aZ5.js.map → button-BdAtIbVu.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{card-CsZzIyPW.js → card-BFYc_QrK.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{card-CsZzIyPW.js.map → card-BFYc_QrK.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/generateCategoricalChart-BwzyqytT.js +70 -0
- package/extensions/dashboard-client/dist/assets/generateCategoricalChart-BwzyqytT.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{index-B1_J_Y0b.js → index-pmtEr3sb.js} +8 -8
- package/extensions/dashboard-client/dist/assets/index-pmtEr3sb.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/index-za_gmEmQ.css +1 -0
- package/extensions/dashboard-client/dist/assets/{switch-DOIA1Nxu.js → switch-DEyGlFwJ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{switch-DOIA1Nxu.js.map → switch-DEyGlFwJ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{toggle-CyS6PHCO.js → toggle-pDMBQYQj.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{toggle-CyS6PHCO.js.map → toggle-pDMBQYQj.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-CY7UIUzm.js → useSSE-BKaC4dao.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-CY7UIUzm.js.map → useSSE-BKaC4dao.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +2 -2
- package/extensions/dashboard-client/package-lock.json +56 -39
- package/extensions/dashboard-client/package.json +3 -0
- package/extensions/dashboard-client/src/api/client.ts +11 -0
- package/extensions/dashboard-client/src/components/SessionContextGauges.tsx +14 -8
- package/extensions/dashboard-client/src/components/WidgetDetailModal.tsx +69 -0
- package/extensions/dashboard-client/src/components/charts/PerfBarChart.tsx +89 -0
- package/extensions/dashboard-client/src/components/charts/PerfLineChart.tsx +108 -0
- package/extensions/dashboard-client/src/components/ui/SortableCard.tsx +75 -0
- package/extensions/dashboard-client/src/hooks/useCardPositions.ts +74 -0
- package/extensions/dashboard-client/src/styles/session-gauges.css +28 -15
- package/extensions/dashboard-client/src/tabs/OverviewTab.tsx +310 -79
- package/extensions/dashboard-client/src/types/card.ts +40 -0
- package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +10 -0
- package/extensions/dashboard-server/api-contracts/endpoints/types.ts +36 -0
- package/extensions/dashboard-server/api-contracts/endpoints.ts +3 -0
- package/extensions/dashboard-server/api-contracts/index.ts +3 -0
- package/extensions/dashboard-server/resolve-snapshot.ts +22 -10
- package/extensions/dashboard-server/routes-game.ts +72 -0
- package/extensions/dashboard-server/routes.ts +1 -0
- package/extensions/dashboard-server/server.ts +2 -0
- package/package.json +1 -1
- package/extensions/dashboard-client/dist/assets/AreaChart-BUUketyS.js +0 -70
- package/extensions/dashboard-client/dist/assets/AreaChart-BUUketyS.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/MetricsTab-Dd1kXuvg.js +0 -2
- package/extensions/dashboard-client/dist/assets/MetricsTab-Dd1kXuvg.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/OverviewTab-DOpObVA9.js +0 -2
- package/extensions/dashboard-client/dist/assets/OverviewTab-DOpObVA9.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/SessionsTab-CcFzDJ7W.js +0 -2
- package/extensions/dashboard-client/dist/assets/WikiTab-RHoVt95g.js +0 -2
- package/extensions/dashboard-client/dist/assets/index-B1_J_Y0b.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/index-C5TGTOd6.css +0 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/hooks/useCardPositions.ts — persisted, deterministic
|
|
3
|
+
* card order for the Overview tab.
|
|
4
|
+
*
|
|
5
|
+
* The order is read from localStorage on mount, validated against
|
|
6
|
+
* DEFAULT_CARD_ORDER (must contain exactly the same CardIds — no missing,
|
|
7
|
+
* no extras — and no duplicate ids), and falls back to the default if invalid.
|
|
8
|
+
* Pure deterministic: no Date.now() / Math.random() anywhere.
|
|
9
|
+
*/
|
|
10
|
+
import { useCallback, useState } from "react";
|
|
11
|
+
import type { CardId } from "../types/card";
|
|
12
|
+
import { DEFAULT_CARD_ORDER } from "../types/card";
|
|
13
|
+
|
|
14
|
+
const STORAGE_KEY = "mega-compact-card-order";
|
|
15
|
+
|
|
16
|
+
/** Returns true when `ids` is a valid permutation of DEFAULT_CARD_ORDER. */
|
|
17
|
+
function isValidOrder(ids: unknown): ids is CardId[] {
|
|
18
|
+
if (!Array.isArray(ids)) return false;
|
|
19
|
+
if (ids.length !== DEFAULT_CARD_ORDER.length) return false;
|
|
20
|
+
|
|
21
|
+
const expected = new Set<CardId>(DEFAULT_CARD_ORDER);
|
|
22
|
+
const seen = new Set<CardId>();
|
|
23
|
+
|
|
24
|
+
for (const id of ids) {
|
|
25
|
+
// every entry must be a known CardId
|
|
26
|
+
if (typeof id !== "string" || !expected.has(id as CardId)) return false;
|
|
27
|
+
// no duplicates
|
|
28
|
+
if (seen.has(id as CardId)) return false;
|
|
29
|
+
seen.add(id as CardId);
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Loads a persisted order from localStorage, validating it first. */
|
|
35
|
+
function loadOrder(): CardId[] {
|
|
36
|
+
try {
|
|
37
|
+
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
38
|
+
if (raw == null) return DEFAULT_CARD_ORDER;
|
|
39
|
+
const parsed: unknown = JSON.parse(raw);
|
|
40
|
+
return isValidOrder(parsed) ? parsed : DEFAULT_CARD_ORDER;
|
|
41
|
+
} catch {
|
|
42
|
+
return DEFAULT_CARD_ORDER;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface UseCardPositionsResult {
|
|
47
|
+
order: CardId[];
|
|
48
|
+
moveCard: (id: CardId, overId: CardId) => void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function useCardPositions(): UseCardPositionsResult {
|
|
52
|
+
const [order, setOrder] = useState<CardId[]>(loadOrder);
|
|
53
|
+
|
|
54
|
+
const moveCard = useCallback((id: CardId, overId: CardId) => {
|
|
55
|
+
setOrder((prev) => {
|
|
56
|
+
const from = prev.indexOf(id);
|
|
57
|
+
const to = prev.indexOf(overId);
|
|
58
|
+
if (from === -1 || to === -1 || from === to) return prev;
|
|
59
|
+
|
|
60
|
+
const next = [...prev];
|
|
61
|
+
next.splice(from, 1);
|
|
62
|
+
next.splice(to, 0, id);
|
|
63
|
+
try {
|
|
64
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
|
|
65
|
+
} catch {
|
|
66
|
+
// storage unavailable (private mode / quota) — ignore; order
|
|
67
|
+
// still holds for the session
|
|
68
|
+
}
|
|
69
|
+
return next;
|
|
70
|
+
});
|
|
71
|
+
}, []);
|
|
72
|
+
|
|
73
|
+
return { order, moveCard };
|
|
74
|
+
}
|
|
@@ -11,20 +11,27 @@
|
|
|
11
11
|
grid-column: 1 / -1;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/* Full-width grid: each gauge gets plenty of room, wraps across the whole
|
|
15
|
+
* row. If there are more sessions than columns, the outer wrapper scrolls
|
|
16
|
+
* horizontally rather than cramping the gauges (see .session-gauges-scroll). */
|
|
14
17
|
.session-gauges-grid {
|
|
15
18
|
display: grid;
|
|
16
|
-
grid-template-columns: repeat(auto-fill, minmax(
|
|
17
|
-
gap:
|
|
19
|
+
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
|
20
|
+
gap: 16px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.session-gauges-scroll {
|
|
24
|
+
overflow-x: auto;
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
.session-gauge {
|
|
21
28
|
display: flex;
|
|
22
29
|
flex-direction: column;
|
|
23
|
-
gap:
|
|
24
|
-
padding:
|
|
30
|
+
gap: 8px;
|
|
31
|
+
padding: 14px 16px;
|
|
25
32
|
background: var(--bg);
|
|
26
33
|
border: 1px solid var(--border);
|
|
27
|
-
border-radius:
|
|
34
|
+
border-radius: 8px;
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
.session-gauge-self {
|
|
@@ -37,17 +44,18 @@
|
|
|
37
44
|
display: flex;
|
|
38
45
|
justify-content: space-between;
|
|
39
46
|
align-items: baseline;
|
|
40
|
-
gap:
|
|
47
|
+
gap: 8px;
|
|
41
48
|
}
|
|
42
49
|
|
|
50
|
+
/* Repo/session label — readable 14px. Not truncated by default (full repo
|
|
51
|
+
* names matter); the ellipsis is applied only as a last resort via a
|
|
52
|
+
* truncate+title pattern on the component when space is genuinely tight. */
|
|
43
53
|
.session-gauge-label {
|
|
44
|
-
font-size:
|
|
54
|
+
font-size: 14px;
|
|
45
55
|
font-weight: 600;
|
|
56
|
+
line-height: 1.25;
|
|
46
57
|
color: var(--text);
|
|
47
|
-
|
|
48
|
-
text-overflow: ellipsis;
|
|
49
|
-
white-space: nowrap;
|
|
50
|
-
max-width: 70%;
|
|
58
|
+
min-width: 0;
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
.session-gauge-self .session-gauge-label {
|
|
@@ -55,23 +63,28 @@
|
|
|
55
63
|
}
|
|
56
64
|
|
|
57
65
|
.session-gauge-pct {
|
|
58
|
-
font-size:
|
|
59
|
-
font-weight:
|
|
66
|
+
font-size: 20px;
|
|
67
|
+
font-weight: 700;
|
|
68
|
+
line-height: 1.1;
|
|
60
69
|
color: var(--muted);
|
|
61
70
|
white-space: nowrap;
|
|
71
|
+
flex-shrink: 0;
|
|
62
72
|
}
|
|
63
73
|
|
|
64
74
|
.session-gauges-grid .gauge-bar {
|
|
65
|
-
height:
|
|
75
|
+
height: 12px;
|
|
66
76
|
}
|
|
67
77
|
|
|
68
78
|
.session-gauge-sub {
|
|
69
79
|
margin: 0;
|
|
80
|
+
font-size: 14px;
|
|
81
|
+
font-weight: 500;
|
|
82
|
+
color: var(--text);
|
|
70
83
|
}
|
|
71
84
|
|
|
72
85
|
.session-gauge-meta {
|
|
73
86
|
margin: 0;
|
|
74
|
-
font-size:
|
|
87
|
+
font-size: 12px;
|
|
75
88
|
color: var(--muted);
|
|
76
89
|
overflow: hidden;
|
|
77
90
|
text-overflow: ellipsis;
|
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import type React from "react";
|
|
21
|
-
import { useCallback, useEffect, useMemo } from "react";
|
|
21
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
22
22
|
import { useApi } from "../hooks/useApi";
|
|
23
23
|
import { useSSE } from "../hooks/useSSE";
|
|
24
|
-
import { fetchSessions } from "../api/client";
|
|
24
|
+
import { fetchSessions, fetchPerfSamples } from "../api/client";
|
|
25
25
|
import type {
|
|
26
26
|
SnapshotResponse,
|
|
27
27
|
SseEvent,
|
|
@@ -44,12 +44,37 @@ import { LegendCard } from "../components/LegendCard";
|
|
|
44
44
|
import { Badge } from "../components/ui/badge";
|
|
45
45
|
import { NEW_UI } from "../config";
|
|
46
46
|
import RagHealthCard from "./SetupTab/RagHealthCard";
|
|
47
|
+
import { WidgetDetailModal } from "../components/WidgetDetailModal";
|
|
48
|
+
import { PerfLineChart } from "../components/charts/PerfLineChart";
|
|
49
|
+
import { PerfBarChart } from "../components/charts/PerfBarChart";
|
|
50
|
+
import { DndContext, type DragEndEvent } from "@dnd-kit/core";
|
|
51
|
+
import { SortableContext } from "@dnd-kit/sortable";
|
|
52
|
+
import type { CardId } from "../types/card";
|
|
53
|
+
import { useCardPositions } from "../hooks/useCardPositions";
|
|
54
|
+
import { SortableCard } from "../components/ui/SortableCard";
|
|
47
55
|
|
|
48
56
|
/** Type guard: narrows an SSE event to a session_sample event. */
|
|
49
57
|
function isSessionSample(e: SseEvent): e is SseSessionSample {
|
|
50
58
|
return e.type === "session_sample";
|
|
51
59
|
}
|
|
52
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Maps a widget id to its perf sample kind (null for snapshot-backed widgets,
|
|
63
|
+
* e.g. "model" which derives its drill-down from snapshot data in-component).
|
|
64
|
+
*/
|
|
65
|
+
function perfKindFor(widget: string): string | null {
|
|
66
|
+
switch (widget) {
|
|
67
|
+
case "cache":
|
|
68
|
+
return "cache_hit_pct";
|
|
69
|
+
case "vector":
|
|
70
|
+
return "turn_latency_ms";
|
|
71
|
+
case "time":
|
|
72
|
+
return "disk_write_ms";
|
|
73
|
+
default:
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
53
78
|
export interface OverviewTabProps {
|
|
54
79
|
snapshot: SnapshotResponse | null;
|
|
55
80
|
loading: boolean;
|
|
@@ -95,6 +120,60 @@ export default function OverviewTab({
|
|
|
95
120
|
refetchSessions();
|
|
96
121
|
}, [lastSampleTs, refetchSessions]);
|
|
97
122
|
|
|
123
|
+
// ── Widget drill-down state ─────────────────────────────────────────────
|
|
124
|
+
// which widget's modal is open (null = none)
|
|
125
|
+
const [openWidget, setOpenWidget] = useState<string | null>(null);
|
|
126
|
+
// perf samples fetched for the opened perf-backed widget
|
|
127
|
+
const [perfData, setPerfData] = useState<Array<{ ts: number; value: number }>>([]);
|
|
128
|
+
const [perfLoading, setPerfLoading] = useState(false);
|
|
129
|
+
const [perfError, setPerfError] = useState<Error | null>(null);
|
|
130
|
+
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
setPerfData([]);
|
|
133
|
+
setPerfError(null);
|
|
134
|
+
|
|
135
|
+
const kind = openWidget ? perfKindFor(openWidget) : null;
|
|
136
|
+
if (kind === null) {
|
|
137
|
+
setPerfLoading(false);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let cancelled = false;
|
|
142
|
+
setPerfLoading(true);
|
|
143
|
+
fetchPerfSamples(kind, 60)
|
|
144
|
+
.then((res) => {
|
|
145
|
+
if (!cancelled) {
|
|
146
|
+
setPerfData(res.samples.map((s) => ({ ts: s.ts, value: s.value })));
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
.catch((err) => {
|
|
150
|
+
if (!cancelled) {
|
|
151
|
+
setPerfError(
|
|
152
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
.finally(() => {
|
|
157
|
+
if (!cancelled) setPerfLoading(false);
|
|
158
|
+
});
|
|
159
|
+
return () => {
|
|
160
|
+
cancelled = true;
|
|
161
|
+
};
|
|
162
|
+
}, [openWidget]);
|
|
163
|
+
|
|
164
|
+
// Drag-and-drop card reordering, persisted to localStorage.
|
|
165
|
+
const { order, moveCard } = useCardPositions();
|
|
166
|
+
|
|
167
|
+
const onDragEnd = useCallback(
|
|
168
|
+
(event: DragEndEvent) => {
|
|
169
|
+
const { active, over } = event;
|
|
170
|
+
if (over && active.id !== over.id) {
|
|
171
|
+
moveCard(active.id as CardId, over.id as CardId);
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
[moveCard],
|
|
175
|
+
);
|
|
176
|
+
|
|
98
177
|
if (loading && !snapshot)
|
|
99
178
|
return <div className="tab-stub">Loading snapshot…</div>;
|
|
100
179
|
if (error && !snapshot)
|
|
@@ -113,6 +192,155 @@ export default function OverviewTab({
|
|
|
113
192
|
const repo = d.repo;
|
|
114
193
|
const model = d.model;
|
|
115
194
|
|
|
195
|
+
// Per-model cost breakdown for the Model & Cost drill-down, derived from
|
|
196
|
+
// snapshot data already in the component (no snapshot.byModel exists).
|
|
197
|
+
const modelBarData = useMemo(
|
|
198
|
+
() => [
|
|
199
|
+
{ label: "Tokens freed", value: compression.repo.tokensFreed },
|
|
200
|
+
{ label: "Input rate", value: model ? model.inputRate : 0 },
|
|
201
|
+
{ label: "Output rate", value: model ? model.outputRate : 0 },
|
|
202
|
+
],
|
|
203
|
+
[compression, model],
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// Renders a single card (wrapped in SortableCard) by its CardId. Cards
|
|
207
|
+
// with a drill-down get an onClick; the rest are plain sortable items.
|
|
208
|
+
const renderCard = (cardId: CardId): React.ReactNode => {
|
|
209
|
+
switch (cardId) {
|
|
210
|
+
case "trigger":
|
|
211
|
+
return (
|
|
212
|
+
<SortableCard id={cardId} onClick={() => setOpenWidget("cache")}>
|
|
213
|
+
<TriggerStatus
|
|
214
|
+
armed={trigger.armed}
|
|
215
|
+
ready={trigger.ready}
|
|
216
|
+
currentTokens={trigger.currentTokens}
|
|
217
|
+
thresholdTokens={trigger.thresholdTokens}
|
|
218
|
+
fastGatePct={trigger.fastGatePct}
|
|
219
|
+
/>
|
|
220
|
+
</SortableCard>
|
|
221
|
+
);
|
|
222
|
+
case "vector":
|
|
223
|
+
return (
|
|
224
|
+
<SortableCard id={cardId} onClick={() => setOpenWidget("vector")}>
|
|
225
|
+
<VectorStoreCard
|
|
226
|
+
checkpointCount={store.checkpointCount}
|
|
227
|
+
tokensIn={compression.session.tokensIn}
|
|
228
|
+
tokensOut={compression.session.tokensOut}
|
|
229
|
+
tokensFreed={compression.session.tokensFreed}
|
|
230
|
+
injectedCount={store.injectedCount}
|
|
231
|
+
dedupHitRate={store.dedupHitRate}
|
|
232
|
+
storageDedupRate={store.storageDedupRate}
|
|
233
|
+
dedupCollapsed={store.dedupCollapsed}
|
|
234
|
+
lastCheckpointId={session.lastCheckpointId}
|
|
235
|
+
compressionPct={compression.session.compressionPct}
|
|
236
|
+
dedupPct={compression.session.dedupPct}
|
|
237
|
+
/>
|
|
238
|
+
</SortableCard>
|
|
239
|
+
);
|
|
240
|
+
case "repo-all":
|
|
241
|
+
return (
|
|
242
|
+
<SortableCard id={cardId}>
|
|
243
|
+
<RepoAllSessionsCard
|
|
244
|
+
checkpointCount={repo.checkpointCount}
|
|
245
|
+
tokensIn={compression.repo.tokensIn}
|
|
246
|
+
tokensOut={compression.repo.tokensOut}
|
|
247
|
+
tokensFreed={compression.repo.tokensFreed}
|
|
248
|
+
sessionCount={repo.sessionCount}
|
|
249
|
+
dedupCollapsed={repo.dedupCollapsed}
|
|
250
|
+
storageDedupRate={repo.storageDedupRate}
|
|
251
|
+
compressionPct={compression.repo.compressionPct}
|
|
252
|
+
dedupPct={compression.repo.dedupPct}
|
|
253
|
+
/>
|
|
254
|
+
</SortableCard>
|
|
255
|
+
);
|
|
256
|
+
case "data-safety":
|
|
257
|
+
return (
|
|
258
|
+
<SortableCard id={cardId}>
|
|
259
|
+
<DataSafetyCard
|
|
260
|
+
regionsRetained={integrity.regionsRetained}
|
|
261
|
+
compressedOriginalBytes={integrity.compressedOriginalBytes}
|
|
262
|
+
duplicatesCollapsed={integrity.duplicatesCollapsed}
|
|
263
|
+
bytesPermanentlyDeleted={integrity.bytesPermanentlyDeleted}
|
|
264
|
+
/>
|
|
265
|
+
</SortableCard>
|
|
266
|
+
);
|
|
267
|
+
case "config":
|
|
268
|
+
return (
|
|
269
|
+
<SortableCard id={cardId}>
|
|
270
|
+
<ConfigSummaryCard
|
|
271
|
+
tier={tier}
|
|
272
|
+
presetTier={d.presetTier}
|
|
273
|
+
pressure={d.pressure}
|
|
274
|
+
thresholdTokens={cfg.thresholdTokens}
|
|
275
|
+
tierPct={cfg.tierPct}
|
|
276
|
+
contextWindow={context.contextWindow}
|
|
277
|
+
fastGatePct={cfg.fastGatePct}
|
|
278
|
+
auto={cfg.auto}
|
|
279
|
+
anchorUserMessages={cfg.anchorUserMessages}
|
|
280
|
+
/>
|
|
281
|
+
</SortableCard>
|
|
282
|
+
);
|
|
283
|
+
case "model":
|
|
284
|
+
return model ? (
|
|
285
|
+
<SortableCard id={cardId} onClick={() => setOpenWidget("model")}>
|
|
286
|
+
<CacheStatusPerModel
|
|
287
|
+
name={model.name}
|
|
288
|
+
provider={model.providerName || model.provider}
|
|
289
|
+
inputRate={model.inputRate}
|
|
290
|
+
outputRate={model.outputRate}
|
|
291
|
+
repoTokensFreed={compression.repo.tokensFreed}
|
|
292
|
+
contextWindow={context.contextWindow}
|
|
293
|
+
/>
|
|
294
|
+
</SortableCard>
|
|
295
|
+
) : null;
|
|
296
|
+
case "crew":
|
|
297
|
+
return (
|
|
298
|
+
<SortableCard id={cardId}>
|
|
299
|
+
<SessionInfo
|
|
300
|
+
activeAgents={crew.activeAgents}
|
|
301
|
+
currentTurn={crew.currentTurn}
|
|
302
|
+
/>
|
|
303
|
+
</SortableCard>
|
|
304
|
+
);
|
|
305
|
+
case "cache-hits":
|
|
306
|
+
return (
|
|
307
|
+
<SortableCard id={cardId} onClick={() => setOpenWidget("cache")}>
|
|
308
|
+
<CacheHitsCard
|
|
309
|
+
cacheHitsSession={cacheHits.session}
|
|
310
|
+
cacheHitsTotal={cacheHits.total}
|
|
311
|
+
tokensSavedSession={cacheHits.sessionTokensSaved}
|
|
312
|
+
tokensSavedTotal={cacheHits.totalTokensSaved}
|
|
313
|
+
compactionsSession={compacts.session}
|
|
314
|
+
compactionsTotal={compacts.total}
|
|
315
|
+
/>
|
|
316
|
+
</SortableCard>
|
|
317
|
+
);
|
|
318
|
+
case "time-saved":
|
|
319
|
+
return (
|
|
320
|
+
<SortableCard id={cardId} onClick={() => setOpenWidget("time")}>
|
|
321
|
+
<TimeSavedCard
|
|
322
|
+
compactSessionSec={timeSaved.compact.sessionSec}
|
|
323
|
+
compactTotalSec={timeSaved.compact.totalSec}
|
|
324
|
+
cacheHitSessionSec={timeSaved.cacheHit.sessionSec}
|
|
325
|
+
cacheHitTotalSec={timeSaved.cacheHit.totalSec}
|
|
326
|
+
/>
|
|
327
|
+
</SortableCard>
|
|
328
|
+
);
|
|
329
|
+
case "rag-health":
|
|
330
|
+
return NEW_UI() ? (
|
|
331
|
+
<SortableCard id={cardId}>
|
|
332
|
+
<RagHealthCard />
|
|
333
|
+
</SortableCard>
|
|
334
|
+
) : null;
|
|
335
|
+
case "legend":
|
|
336
|
+
return (
|
|
337
|
+
<SortableCard id={cardId}>
|
|
338
|
+
<LegendCard />
|
|
339
|
+
</SortableCard>
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
|
|
116
344
|
return (
|
|
117
345
|
<div className="flex flex-col gap-4">
|
|
118
346
|
<div className="flex items-center justify-between">
|
|
@@ -121,7 +349,9 @@ export default function OverviewTab({
|
|
|
121
349
|
updated {formatUpdatedAt(updatedAt)}
|
|
122
350
|
</span>
|
|
123
351
|
</div>
|
|
124
|
-
|
|
352
|
+
{/* Full-width live-context row: per-session gauges + per-repo stack
|
|
353
|
+
* sit above the grid so they get the whole width of the tab. */}
|
|
354
|
+
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
|
125
355
|
<SessionContextGauges
|
|
126
356
|
sessions={sessionsData}
|
|
127
357
|
loading={sessionsLoading}
|
|
@@ -138,85 +368,86 @@ export default function OverviewTab({
|
|
|
138
368
|
loading={sessionsLoading}
|
|
139
369
|
error={sessionsErr}
|
|
140
370
|
/>
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
dedupPct={compression.repo.dedupPct}
|
|
171
|
-
/>
|
|
172
|
-
<DataSafetyCard
|
|
173
|
-
regionsRetained={integrity.regionsRetained}
|
|
174
|
-
compressedOriginalBytes={integrity.compressedOriginalBytes}
|
|
175
|
-
duplicatesCollapsed={integrity.duplicatesCollapsed}
|
|
176
|
-
bytesPermanentlyDeleted={integrity.bytesPermanentlyDeleted}
|
|
177
|
-
/>
|
|
178
|
-
<ConfigSummaryCard
|
|
179
|
-
tier={tier}
|
|
180
|
-
presetTier={d.presetTier}
|
|
181
|
-
pressure={d.pressure}
|
|
182
|
-
thresholdTokens={cfg.thresholdTokens}
|
|
183
|
-
tierPct={cfg.tierPct}
|
|
184
|
-
contextWindow={context.contextWindow}
|
|
185
|
-
fastGatePct={cfg.fastGatePct}
|
|
186
|
-
auto={cfg.auto}
|
|
187
|
-
anchorUserMessages={cfg.anchorUserMessages}
|
|
188
|
-
/>
|
|
189
|
-
{model && (
|
|
190
|
-
<CacheStatusPerModel
|
|
191
|
-
name={model.name}
|
|
192
|
-
provider={model.providerName || model.provider}
|
|
193
|
-
inputRate={model.inputRate}
|
|
194
|
-
outputRate={model.outputRate}
|
|
195
|
-
repoTokensFreed={compression.repo.tokensFreed}
|
|
196
|
-
contextWindow={context.contextWindow}
|
|
371
|
+
</div>
|
|
372
|
+
<DndContext onDragEnd={onDragEnd}>
|
|
373
|
+
<SortableContext items={order}>
|
|
374
|
+
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
|
|
375
|
+
{order
|
|
376
|
+
.map((cardId) => ({ cardId, node: renderCard(cardId) }))
|
|
377
|
+
.filter(({ node }) => node != null)
|
|
378
|
+
.map(({ cardId, node }) => (
|
|
379
|
+
<div key={cardId}>{node}</div>
|
|
380
|
+
))}
|
|
381
|
+
</div>
|
|
382
|
+
</SortableContext>
|
|
383
|
+
</DndContext>
|
|
384
|
+
|
|
385
|
+
{/* ── Drill-down modals ───────────────────────────────────────── */}
|
|
386
|
+
<WidgetDetailModal
|
|
387
|
+
title="Cache Hits Trend"
|
|
388
|
+
open={openWidget === "cache"}
|
|
389
|
+
onClose={() => setOpenWidget(null)}
|
|
390
|
+
>
|
|
391
|
+
{perfLoading ? (
|
|
392
|
+
<p className="py-8 text-center text-sm text-muted-foreground">Loading…</p>
|
|
393
|
+
) : perfError ? (
|
|
394
|
+
<p className="py-8 text-center text-sm text-red-400">{perfError.message}</p>
|
|
395
|
+
) : (
|
|
396
|
+
<PerfLineChart
|
|
397
|
+
data={perfData}
|
|
398
|
+
label="Cache hit %"
|
|
399
|
+
color="#58a6ff"
|
|
197
400
|
/>
|
|
198
401
|
)}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
402
|
+
</WidgetDetailModal>
|
|
403
|
+
|
|
404
|
+
<WidgetDetailModal
|
|
405
|
+
title="Turn Latency"
|
|
406
|
+
open={openWidget === "vector"}
|
|
407
|
+
onClose={() => setOpenWidget(null)}
|
|
408
|
+
>
|
|
409
|
+
{perfLoading ? (
|
|
410
|
+
<p className="py-8 text-center text-sm text-muted-foreground">Loading…</p>
|
|
411
|
+
) : perfError ? (
|
|
412
|
+
<p className="py-8 text-center text-sm text-red-400">{perfError.message}</p>
|
|
413
|
+
) : (
|
|
414
|
+
<PerfLineChart
|
|
415
|
+
data={perfData}
|
|
416
|
+
label="Turn latency (ms)"
|
|
417
|
+
color="#58a6ff"
|
|
418
|
+
/>
|
|
419
|
+
)}
|
|
420
|
+
</WidgetDetailModal>
|
|
421
|
+
|
|
422
|
+
<WidgetDetailModal
|
|
423
|
+
title="Disk Write Time"
|
|
424
|
+
open={openWidget === "time"}
|
|
425
|
+
onClose={() => setOpenWidget(null)}
|
|
426
|
+
>
|
|
427
|
+
{perfLoading ? (
|
|
428
|
+
<p className="py-8 text-center text-sm text-muted-foreground">Loading…</p>
|
|
429
|
+
) : perfError ? (
|
|
430
|
+
<p className="py-8 text-center text-sm text-red-400">{perfError.message}</p>
|
|
431
|
+
) : (
|
|
432
|
+
<PerfLineChart
|
|
433
|
+
data={perfData}
|
|
434
|
+
label="Disk write (ms)"
|
|
435
|
+
color="#58a6ff"
|
|
436
|
+
/>
|
|
437
|
+
)}
|
|
438
|
+
</WidgetDetailModal>
|
|
439
|
+
|
|
440
|
+
<WidgetDetailModal
|
|
441
|
+
title="Model & Cost"
|
|
442
|
+
open={openWidget === "model"}
|
|
443
|
+
onClose={() => setOpenWidget(null)}
|
|
444
|
+
>
|
|
445
|
+
<PerfBarChart
|
|
446
|
+
data={modelBarData}
|
|
447
|
+
color="#3fb950"
|
|
448
|
+
valueFormatter={(v) => v.toLocaleString()}
|
|
216
449
|
/>
|
|
217
|
-
|
|
218
|
-
<LegendCard />
|
|
219
|
-
</div>
|
|
450
|
+
</WidgetDetailModal>
|
|
220
451
|
</div>
|
|
221
452
|
);
|
|
222
453
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/types/card.ts — Overview tab card identity + fixed order.
|
|
3
|
+
*
|
|
4
|
+
* Every card on the Overview grid maps to a stable CardId. The default order
|
|
5
|
+
* is the canonical render order; the user can reorder via drag-and-drop and
|
|
6
|
+
* their order is persisted by useCardPositions.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Identifies each card on the Overview grid. */
|
|
10
|
+
export type CardId =
|
|
11
|
+
| "trigger"
|
|
12
|
+
| "vector"
|
|
13
|
+
| "repo-all"
|
|
14
|
+
| "data-safety"
|
|
15
|
+
| "config"
|
|
16
|
+
| "model"
|
|
17
|
+
| "crew"
|
|
18
|
+
| "cache-hits"
|
|
19
|
+
| "time-saved"
|
|
20
|
+
| "rag-health"
|
|
21
|
+
| "legend";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Canonical default order of the Overview cards, matching the pre-dnd render
|
|
25
|
+
* order. Any persisted order must contain exactly these ids (no extras, no
|
|
26
|
+
* missing) to be accepted.
|
|
27
|
+
*/
|
|
28
|
+
export const DEFAULT_CARD_ORDER: CardId[] = [
|
|
29
|
+
"trigger",
|
|
30
|
+
"vector",
|
|
31
|
+
"repo-all",
|
|
32
|
+
"data-safety",
|
|
33
|
+
"config",
|
|
34
|
+
"model",
|
|
35
|
+
"crew",
|
|
36
|
+
"cache-hits",
|
|
37
|
+
"time-saved",
|
|
38
|
+
"rag-health",
|
|
39
|
+
"legend",
|
|
40
|
+
];
|
|
@@ -74,6 +74,8 @@ import type {
|
|
|
74
74
|
ServersResponse,
|
|
75
75
|
PerfQuery,
|
|
76
76
|
PerfResponse,
|
|
77
|
+
PerfSamplesQuery,
|
|
78
|
+
PerfSamplesResponse,
|
|
77
79
|
} from "./types.js";
|
|
78
80
|
|
|
79
81
|
// ─── ENDPOINTS Registry ─────────────────────────────────────────────────────
|
|
@@ -184,6 +186,14 @@ export const ENDPOINTS = {
|
|
|
184
186
|
description: "Rolling-window performance aggregates over perf_samples.",
|
|
185
187
|
} as const satisfies EndpointDef<"GET", PerfQuery, PerfResponse>,
|
|
186
188
|
|
|
189
|
+
/** GET /api/perf/samples — Raw perf samples for a single kind (chart drill-down). */
|
|
190
|
+
perfSamples: {
|
|
191
|
+
method: "GET",
|
|
192
|
+
path: "/api/perf/samples",
|
|
193
|
+
description:
|
|
194
|
+
"Raw perf_samples for one kind (time-series drill-down across any perf metric).",
|
|
195
|
+
} as const satisfies EndpointDef<"GET", PerfSamplesQuery, PerfSamplesResponse>,
|
|
196
|
+
|
|
187
197
|
/** GET /api/achievements — Achievement tiles with unlock state. */
|
|
188
198
|
achievements: {
|
|
189
199
|
method: "GET",
|