pi-mega-compact 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/extensions/dashboard-server/api-contracts/endpoints/registry.js +6 -0
- package/dist/extensions/dashboard-server/routes-cache.js +17 -5
- 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/dist/extensions/mega-runtime/capture-model.js +15 -2
- package/dist/extensions/mega-runtime/runtime-snapshot.js +26 -0
- package/dist/extensions/mega-runtime/runtime.js +1 -0
- 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-CDyZqMO-.js → CacheTab-EhqkEglo.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-CDyZqMO-.js.map → CacheTab-EhqkEglo.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-ByPYMJVG.js → EventsTab-BUACwWXf.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-ByPYMJVG.js.map → EventsTab-BUACwWXf.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-CeHQR0py.js → HealthTab-CDEYVRBb.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-CeHQR0py.js.map → HealthTab-CDEYVRBb.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-ceTD_c1A.js → MaintenanceTab-DtanIeCQ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-ceTD_c1A.js.map → MaintenanceTab-DtanIeCQ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CRUdVR7u.js → MemoryMapTab-BGEhL7OG.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CRUdVR7u.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-DUrP7Efa.js → ReposTab-Bg72Ozdc.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-DUrP7Efa.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-Cg-IsijD.js.map → SessionsTab-BcTK9Lg6.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-ZE1daXjN.js → SetupTab-Bt-Ru1xp.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-ZE1daXjN.js.map → SetupTab-Bt-Ru1xp.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BkTgi3Af.js → TimeSavedCard-ic_LjJ79.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BkTgi3Af.js.map → TimeSavedCard-ic_LjJ79.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-CZvudHQF.js → TurnsTab-D9AZTMgG.js} +4 -4
- package/extensions/dashboard-client/dist/assets/{TurnsTab-CZvudHQF.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-BVAnGp8_.js.map → WikiTab-hRcJPi_m.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{button-CUrzhi2A.js → button-BdAtIbVu.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{button-CUrzhi2A.js.map → button-BdAtIbVu.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{card-BsfojHfp.js → card-BFYc_QrK.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{card-BsfojHfp.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-OM3w-l2T.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-qSz2Un3t.js → switch-DEyGlFwJ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{switch-qSz2Un3t.js.map → switch-DEyGlFwJ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{toggle-DyBKcLe-.js → toggle-pDMBQYQj.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{toggle-DyBKcLe-.js.map → toggle-pDMBQYQj.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-DqVouqww.js → useSSE-BKaC4dao.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-DqVouqww.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 -0
- 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/layout/AppShell.tsx +1 -1
- 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/base.css +2 -4
- 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-client/tailwind.config.js +1 -1
- 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/routes-cache.ts +17 -5
- 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/extensions/mega-runtime/capture-model.ts +14 -2
- package/extensions/mega-runtime/runtime-snapshot.ts +40 -0
- package/extensions/mega-runtime/runtime.ts +1 -0
- package/package.json +1 -1
- package/extensions/dashboard-client/dist/assets/AreaChart-C68tGr2J.js +0 -70
- package/extensions/dashboard-client/dist/assets/AreaChart-C68tGr2J.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/MetricsTab-DP7Kmdu9.js +0 -2
- package/extensions/dashboard-client/dist/assets/MetricsTab-DP7Kmdu9.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/OverviewTab-4CGP0djv.js +0 -2
- package/extensions/dashboard-client/dist/assets/OverviewTab-4CGP0djv.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/SessionsTab-Cg-IsijD.js +0 -2
- package/extensions/dashboard-client/dist/assets/WikiTab-BVAnGp8_.js +0 -2
- package/extensions/dashboard-client/dist/assets/index-CUA6EDRF.css +0 -1
- package/extensions/dashboard-client/dist/assets/index-OM3w-l2T.js.map +0 -1
|
@@ -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",
|
|
@@ -348,3 +348,39 @@ export interface PerfResponse {
|
|
|
348
348
|
/** Diagnostic counters from the live snapshot, or null if unavailable. */
|
|
349
349
|
readonly diag: PerfDiag | null;
|
|
350
350
|
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Query parameters for GET /api/perf/samples. `kind` selects which perf metric
|
|
354
|
+
* to return raw samples for; `minutes` controls the rolling window size.
|
|
355
|
+
*/
|
|
356
|
+
export interface PerfSamplesQuery {
|
|
357
|
+
/** Perf metric kind (turn_latency_ms, tps, disk_write_ms, cache_hit_pct, ...). Required. */
|
|
358
|
+
readonly kind: string;
|
|
359
|
+
/** Rolling window size in minutes (default: 60). Optional. */
|
|
360
|
+
readonly minutes?: number;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** A single raw perf sample for time-series charting. */
|
|
364
|
+
export interface PerfSamplePoint {
|
|
365
|
+
/** Epoch ms timestamp of the sample. */
|
|
366
|
+
readonly ts: number;
|
|
367
|
+
/** Sample value for the requested kind. */
|
|
368
|
+
readonly value: number;
|
|
369
|
+
/** Optional stringified metadata associated with the sample. */
|
|
370
|
+
readonly meta?: string;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Response for GET /api/perf/samples. Raw perf samples for a single kind,
|
|
375
|
+
* ascending by ts, for chart drill-down in the Perf tab.
|
|
376
|
+
*/
|
|
377
|
+
export interface PerfSamplesResponse {
|
|
378
|
+
/** Array of raw samples ascending by ts. */
|
|
379
|
+
readonly samples: PerfSamplePoint[];
|
|
380
|
+
/** The requested perf kind. */
|
|
381
|
+
readonly kind: string;
|
|
382
|
+
/** The rolling window size in minutes used. */
|
|
383
|
+
readonly minutes: number;
|
|
384
|
+
/** Epoch ms timestamp when the response was generated. */
|
|
385
|
+
readonly updatedAt: number;
|
|
386
|
+
}
|