pi-mega-compact 0.8.12 → 0.8.14
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/extensions/dashboard-client/dist/assets/AchievementTiles-BH26e95w.js +2 -0
- package/extensions/dashboard-client/dist/assets/AchievementTiles-BH26e95w.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/AchievementsTab-B94wvRV_.js +2 -0
- package/extensions/dashboard-client/dist/assets/AchievementsTab-B94wvRV_.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/CacheTab-DPmw3NHR.js +2 -0
- package/extensions/dashboard-client/dist/assets/CacheTab-DPmw3NHR.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/ConfigTab-C3g3smRL.js +2 -0
- package/extensions/dashboard-client/dist/assets/ConfigTab-C3g3smRL.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/EventsTab-B5PicfFw.js +2 -0
- package/extensions/dashboard-client/dist/assets/EventsTab-B5PicfFw.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/GameTab-CsVwp78a.js +2 -0
- package/extensions/dashboard-client/dist/assets/GameTab-CsVwp78a.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/MetricsTab-Cf72UewX.js +2 -0
- package/extensions/dashboard-client/dist/assets/MetricsTab-Cf72UewX.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/OverviewTab-Dh6aG3gy.js +2 -0
- package/extensions/dashboard-client/dist/assets/OverviewTab-Dh6aG3gy.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/ReposTab-HXf2dorl.js +2 -0
- package/extensions/dashboard-client/dist/assets/ReposTab-HXf2dorl.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/TimeSavedCard-kUnoospE.js +2 -0
- package/extensions/dashboard-client/dist/assets/TimeSavedCard-kUnoospE.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/index-Cfxniu2R.css +1 -0
- package/extensions/dashboard-client/dist/assets/{index-XAIN8km6.js → index-DMntLu-8.js} +11 -10
- package/extensions/dashboard-client/dist/assets/index-DMntLu-8.js.map +1 -0
- package/extensions/dashboard-client/dist/index.html +2 -2
- package/extensions/dashboard-client/src/App.tsx +18 -4
- package/extensions/dashboard-client/src/components/AchievementTiles.tsx +105 -0
- package/extensions/dashboard-client/src/components/ActiveReposTable.tsx +116 -0
- package/extensions/dashboard-client/src/components/CacheHitsCard.tsx +73 -0
- package/extensions/dashboard-client/src/components/CacheStatusPerModel.tsx +78 -35
- package/extensions/dashboard-client/src/components/ConfigSummaryCard.tsx +101 -0
- package/extensions/dashboard-client/src/components/ContextGauge.tsx +11 -15
- package/extensions/dashboard-client/src/components/DataSafetyCard.tsx +42 -33
- package/extensions/dashboard-client/src/components/EventCategoryFilter.tsx +46 -9
- package/extensions/dashboard-client/src/components/LegendCard.tsx +49 -0
- package/extensions/dashboard-client/src/components/ModelBadge.tsx +42 -11
- package/extensions/dashboard-client/src/components/PerfCards.tsx +91 -0
- package/extensions/dashboard-client/src/components/RepoAllSessionsCard.tsx +102 -0
- package/extensions/dashboard-client/src/components/RepoDetailModal.tsx +9 -2
- package/extensions/dashboard-client/src/components/RepoTable.tsx +30 -22
- package/extensions/dashboard-client/src/components/SavingsByModelTable.tsx +226 -0
- package/extensions/dashboard-client/src/components/SessionInfo.tsx +25 -50
- package/extensions/dashboard-client/src/components/SummaryTiles.tsx +20 -11
- package/extensions/dashboard-client/src/components/TimeSavedCard.tsx +61 -0
- package/extensions/dashboard-client/src/components/TriggerStatus.tsx +31 -19
- package/extensions/dashboard-client/src/components/VectorStoreCard.tsx +134 -0
- package/extensions/dashboard-client/src/main.tsx +5 -0
- package/extensions/dashboard-client/src/styles/base.css +76 -9
- package/extensions/dashboard-client/src/styles/config.css +149 -0
- package/extensions/dashboard-client/src/styles/game-achievements.css +265 -0
- package/extensions/dashboard-client/src/styles/metrics-extra.css +69 -0
- package/extensions/dashboard-client/src/styles/overview-extra.css +170 -0
- package/extensions/dashboard-client/src/styles/repos-extra.css +99 -0
- package/extensions/dashboard-client/src/styles/repos-metrics.css +14 -23
- package/extensions/dashboard-client/src/tabs/AchievementsTab.tsx +55 -0
- package/extensions/dashboard-client/src/tabs/CacheTab.tsx +50 -0
- package/extensions/dashboard-client/src/tabs/ConfigTab.tsx +240 -5
- package/extensions/dashboard-client/src/tabs/EventsTab.tsx +55 -3
- package/extensions/dashboard-client/src/tabs/GameTab.tsx +287 -0
- package/extensions/dashboard-client/src/tabs/MetricsTab.tsx +7 -1
- package/extensions/dashboard-client/src/tabs/OverviewTab.tsx +103 -17
- package/extensions/dashboard-client/src/tabs/ReposTab.tsx +57 -22
- package/extensions/dashboard-client/src/utils/format.ts +85 -0
- package/extensions/dashboard-client/src/utils/types.ts +27 -0
- package/package.json +1 -1
- package/extensions/dashboard-client/dist/assets/ConfigTab-RrjXWfvr.js +0 -2
- package/extensions/dashboard-client/dist/assets/ConfigTab-RrjXWfvr.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/EventsTab-BH_FxJ08.js +0 -2
- package/extensions/dashboard-client/dist/assets/EventsTab-BH_FxJ08.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/MetricsTab-C-PoFV4p.js +0 -2
- package/extensions/dashboard-client/dist/assets/MetricsTab-C-PoFV4p.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/OverviewTab-Db-qBqFN.js +0 -2
- package/extensions/dashboard-client/dist/assets/OverviewTab-Db-qBqFN.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/ReposTab-DhCjJvuj.js +0 -2
- package/extensions/dashboard-client/dist/assets/ReposTab-DhCjJvuj.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/index-Buer5yxB.css +0 -1
- package/extensions/dashboard-client/dist/assets/index-XAIN8km6.js.map +0 -1
|
@@ -1,16 +1,67 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dashboard-client/src/tabs/EventsTab.tsx — Events tab (C1).
|
|
3
3
|
*
|
|
4
|
-
* Live SSE event stream via useSSE + EventStream with
|
|
5
|
-
* Shows connection status + event count.
|
|
4
|
+
* Live SSE event stream via useSSE + EventStream with category filter chips.
|
|
5
|
+
* Shows connection status + event count. Category filter uses the existing
|
|
6
|
+
* EventCategoryFilter component to filter by all/compact/recall/config/crew/game.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import type React from "react";
|
|
10
|
+
import { useState, useMemo } from "react";
|
|
11
|
+
import type { SseEvent } from "@contracts";
|
|
9
12
|
import { useSSE } from "../hooks/useSSE";
|
|
10
13
|
import { EventStream } from "../components/EventStream";
|
|
14
|
+
import {
|
|
15
|
+
EventCategoryFilter,
|
|
16
|
+
type EventCategory,
|
|
17
|
+
} from "../components/EventCategoryFilter";
|
|
18
|
+
|
|
19
|
+
const COMPACT_TYPES = new Set([
|
|
20
|
+
"compact_start",
|
|
21
|
+
"compact_end",
|
|
22
|
+
"compact_trigger",
|
|
23
|
+
"compact_skip",
|
|
24
|
+
]);
|
|
25
|
+
const CONFIG_TYPES = new Set([
|
|
26
|
+
"config_updated",
|
|
27
|
+
"config_preset",
|
|
28
|
+
"tier_changed",
|
|
29
|
+
"model_changed",
|
|
30
|
+
"pressure_lifted",
|
|
31
|
+
"anchors_updated",
|
|
32
|
+
"checkpoint_persisted",
|
|
33
|
+
]);
|
|
34
|
+
const CREW_TYPES = new Set([
|
|
35
|
+
"crew_presence_changed",
|
|
36
|
+
"crew_turn_changed",
|
|
37
|
+
"crew_bandit_chosen",
|
|
38
|
+
]);
|
|
39
|
+
const GAME_TYPES = new Set([
|
|
40
|
+
"game_ritual_start",
|
|
41
|
+
"game_ritual_stage",
|
|
42
|
+
"game_ritual_end",
|
|
43
|
+
"game_mode_changed",
|
|
44
|
+
"game_render",
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
function matchesCategory(ev: SseEvent, cat: EventCategory): boolean {
|
|
48
|
+
if (cat === "all") return true;
|
|
49
|
+
const t = ev.type;
|
|
50
|
+
if (cat === "compact") return COMPACT_TYPES.has(t);
|
|
51
|
+
if (cat === "recall") return t === "recall_inject";
|
|
52
|
+
if (cat === "config") return CONFIG_TYPES.has(t);
|
|
53
|
+
if (cat === "crew") return CREW_TYPES.has(t);
|
|
54
|
+
return GAME_TYPES.has(t); // cat === "game"
|
|
55
|
+
}
|
|
11
56
|
|
|
12
57
|
export default function EventsTab(): React.ReactElement {
|
|
13
58
|
const { events, status, eventCount } = useSSE();
|
|
59
|
+
const [category, setCategory] = useState<EventCategory>("all");
|
|
60
|
+
|
|
61
|
+
const filtered = useMemo(
|
|
62
|
+
() => events.filter((e) => matchesCategory(e, category)),
|
|
63
|
+
[events, category],
|
|
64
|
+
);
|
|
14
65
|
|
|
15
66
|
const statusCls = `sse-status sse-${status}`;
|
|
16
67
|
const statusLabel =
|
|
@@ -31,7 +82,8 @@ export default function EventsTab(): React.ReactElement {
|
|
|
31
82
|
</span>
|
|
32
83
|
<span className="event-count">{eventCount} events received</span>
|
|
33
84
|
</div>
|
|
34
|
-
<
|
|
85
|
+
<EventCategoryFilter active={category} onFilter={setCategory} />
|
|
86
|
+
<EventStream events={filtered} />
|
|
35
87
|
</div>
|
|
36
88
|
);
|
|
37
89
|
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/tabs/GameTab.tsx — Game tab (NEW).
|
|
3
|
+
*
|
|
4
|
+
* Renders:
|
|
5
|
+
* (1) MEGA CACHE banner (animated gradient, shown when any mega_cache > 100)
|
|
6
|
+
* (2) Opie unlock tile (shown when mega_cache > 100)
|
|
7
|
+
* (3) High Scores header
|
|
8
|
+
* (4) 4 leaderboards in 2×2 grid: Cache %, Dedupe, Turns (LVL badge),
|
|
9
|
+
* MEGA CACHE trophies (firstSeen date meta)
|
|
10
|
+
* (5) Repos count badge via fetchGameScores({metric:'repos',limit:1})
|
|
11
|
+
* (6) Achievements sub-section (AchievementTiles shared component)
|
|
12
|
+
* (7) Game empty state
|
|
13
|
+
* Polls scores + achievements every 15s, game-state every 15s.
|
|
14
|
+
* Applies data-theme accent vars when game_mode_on.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type React from "react";
|
|
18
|
+
import { useState, useEffect, useCallback, useRef } from "react";
|
|
19
|
+
import type { GameScoreRow, AchievementRow } from "@contracts";
|
|
20
|
+
import { fetchGameScores, fetchAchievements, fetchGameState } from "../api/client";
|
|
21
|
+
import { AchievementTiles } from "../components/AchievementTiles";
|
|
22
|
+
|
|
23
|
+
/** Actual flat game state shape from the server (NOT the nested contract). */
|
|
24
|
+
interface FlatGameState {
|
|
25
|
+
game_mode_on: boolean;
|
|
26
|
+
theme: string;
|
|
27
|
+
tui_display_mode: "full" | "minimal";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Metadata object inside a mega_cache GameScoreRow. */
|
|
31
|
+
interface TrophyMeta {
|
|
32
|
+
firstSeenTs?: number;
|
|
33
|
+
firstSeen?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* ── Format helpers (matching html.ts logic) ─────────────────────────── */
|
|
37
|
+
|
|
38
|
+
function fmtPct(v: number): string {
|
|
39
|
+
return (Math.round(v * 10) / 10).toString() + "%";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function fmtDate(ts: number | null | undefined): string {
|
|
43
|
+
return ts != null ? new Date(ts).toLocaleString() : "\u2014";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function trophyMeta(m: unknown): TrophyMeta {
|
|
47
|
+
return m && typeof m === "object" ? (m as TrophyMeta) : {};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function repoBasename(path: string): string {
|
|
51
|
+
return path.split("/").pop() || path;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* ── Leaderboard card ────────────────────────────────────────────────── */
|
|
55
|
+
|
|
56
|
+
interface LeaderboardCardProps {
|
|
57
|
+
title: string;
|
|
58
|
+
rows: GameScoreRow[];
|
|
59
|
+
badge?: React.ReactNode;
|
|
60
|
+
renderValue: (r: GameScoreRow) => React.ReactNode;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function LeaderboardCard({
|
|
64
|
+
title,
|
|
65
|
+
rows,
|
|
66
|
+
badge,
|
|
67
|
+
renderValue,
|
|
68
|
+
}: LeaderboardCardProps): React.ReactElement {
|
|
69
|
+
return (
|
|
70
|
+
<div className="lb-card">
|
|
71
|
+
<h3>
|
|
72
|
+
{title}
|
|
73
|
+
{badge}
|
|
74
|
+
</h3>
|
|
75
|
+
{rows.length === 0 ? (
|
|
76
|
+
<div className="lb-empty">—</div>
|
|
77
|
+
) : (
|
|
78
|
+
<table>
|
|
79
|
+
<tbody>
|
|
80
|
+
{rows.map((r, i) => (
|
|
81
|
+
<tr key={`${r.repo_root}-${i}`}>
|
|
82
|
+
<td title={r.repo_root}>{repoBasename(r.repo_root)}</td>
|
|
83
|
+
<td className="num">{renderValue(r)}</td>
|
|
84
|
+
</tr>
|
|
85
|
+
))}
|
|
86
|
+
</tbody>
|
|
87
|
+
</table>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* ── Main GameTab component ──────────────────────────────────────────── */
|
|
94
|
+
|
|
95
|
+
export default function GameTab(): React.ReactElement {
|
|
96
|
+
const [scores, setScores] = useState<
|
|
97
|
+
Record<string, GameScoreRow[]>
|
|
98
|
+
>({});
|
|
99
|
+
const [reposCount, setReposCount] = useState(0);
|
|
100
|
+
const [achievements, setAchievements] = useState<AchievementRow[]>([]);
|
|
101
|
+
const [gameModeOn, setGameModeOn] = useState(false);
|
|
102
|
+
const [loading, setLoading] = useState(true);
|
|
103
|
+
const [megaToast, setMegaToast] = useState<string | null>(null);
|
|
104
|
+
const lastMegaTsRef = useRef(0);
|
|
105
|
+
|
|
106
|
+
const fetchAll = useCallback(async (): Promise<void> => {
|
|
107
|
+
try {
|
|
108
|
+
const [cache, dedupe, turns, megaCache, repos, ach] =
|
|
109
|
+
await Promise.all([
|
|
110
|
+
fetchGameScores({ metric: "cache", limit: 25 }),
|
|
111
|
+
fetchGameScores({ metric: "dedupe", limit: 25 }),
|
|
112
|
+
fetchGameScores({ metric: "turns", limit: 25 }),
|
|
113
|
+
fetchGameScores({ metric: "mega_cache", limit: 25 }),
|
|
114
|
+
fetchGameScores({ metric: "repos", limit: 1 }),
|
|
115
|
+
fetchAchievements(),
|
|
116
|
+
]);
|
|
117
|
+
|
|
118
|
+
/* Mega-cache toast: new row with value > 100 since last poll */
|
|
119
|
+
const newRow = megaCache.find(
|
|
120
|
+
(r) => r.ts > lastMegaTsRef.current && r.value > 100,
|
|
121
|
+
);
|
|
122
|
+
if (lastMegaTsRef.current > 0 && newRow) {
|
|
123
|
+
setMegaToast(
|
|
124
|
+
`oopsie! cache went to ${Math.round(newRow.value)}% — MEGA CACHE \uD83E\uDD67`,
|
|
125
|
+
);
|
|
126
|
+
setTimeout(() => setMegaToast(null), 4000);
|
|
127
|
+
}
|
|
128
|
+
if (megaCache.length > 0) {
|
|
129
|
+
lastMegaTsRef.current = Math.max(
|
|
130
|
+
...megaCache.map((r) => r.ts),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
setScores({ cache, dedupe, turns, mega_cache: megaCache });
|
|
135
|
+
setReposCount(repos[0]?.value ?? 0);
|
|
136
|
+
setAchievements(ach);
|
|
137
|
+
} catch {
|
|
138
|
+
/* non-fatal */
|
|
139
|
+
} finally {
|
|
140
|
+
setLoading(false);
|
|
141
|
+
}
|
|
142
|
+
}, []);
|
|
143
|
+
|
|
144
|
+
/* Poll scores + achievements every 15s. */
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
let active = true;
|
|
147
|
+
const doFetch = async (): Promise<void> => {
|
|
148
|
+
if (active) await fetchAll();
|
|
149
|
+
};
|
|
150
|
+
void doFetch();
|
|
151
|
+
const timer = setInterval(() => void doFetch(), 15000);
|
|
152
|
+
return () => {
|
|
153
|
+
active = false;
|
|
154
|
+
clearInterval(timer);
|
|
155
|
+
};
|
|
156
|
+
}, [fetchAll]);
|
|
157
|
+
|
|
158
|
+
/* Poll game state every 15s for styling. */
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
let active = true;
|
|
161
|
+
const doFetch = async (): Promise<void> => {
|
|
162
|
+
try {
|
|
163
|
+
const gs = (await fetchGameState()) as unknown as FlatGameState;
|
|
164
|
+
if (active) {
|
|
165
|
+
setGameModeOn(!!gs.game_mode_on);
|
|
166
|
+
if (gs.theme)
|
|
167
|
+
document.documentElement.dataset.theme = gs.theme;
|
|
168
|
+
}
|
|
169
|
+
} catch {
|
|
170
|
+
/* non-fatal */
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
void doFetch();
|
|
174
|
+
const timer = setInterval(() => void doFetch(), 15000);
|
|
175
|
+
return () => {
|
|
176
|
+
active = false;
|
|
177
|
+
clearInterval(timer);
|
|
178
|
+
};
|
|
179
|
+
}, []);
|
|
180
|
+
|
|
181
|
+
/* Compute mega-cache banner data */
|
|
182
|
+
const megaRows = scores.mega_cache ?? [];
|
|
183
|
+
let bestMega: number | null = null;
|
|
184
|
+
let firstSeen: number | null = null;
|
|
185
|
+
for (const r of megaRows) {
|
|
186
|
+
if (bestMega == null || r.value > bestMega) bestMega = r.value;
|
|
187
|
+
const m = trophyMeta(r.meta);
|
|
188
|
+
const fs = m.firstSeenTs ?? m.firstSeen ?? r.ts;
|
|
189
|
+
if (firstSeen == null || fs < firstSeen) firstSeen = fs;
|
|
190
|
+
}
|
|
191
|
+
const showMega = bestMega != null && bestMega > 100;
|
|
192
|
+
|
|
193
|
+
/* Turns level badge */
|
|
194
|
+
const turnsRows = scores.turns ?? [];
|
|
195
|
+
const maxTurns = turnsRows.reduce((mx, r) => Math.max(mx, r.value), 0);
|
|
196
|
+
const lvl = Math.floor(Math.log2(maxTurns + 1)) + 1;
|
|
197
|
+
|
|
198
|
+
/* Game empty state */
|
|
199
|
+
const hasData =
|
|
200
|
+
(scores.cache?.length ?? 0) > 0 ||
|
|
201
|
+
(scores.dedupe?.length ?? 0) > 0 ||
|
|
202
|
+
turnsRows.length > 0 ||
|
|
203
|
+
megaRows.length > 0 ||
|
|
204
|
+
reposCount > 0;
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<div className={`game-tab${gameModeOn ? " game-mode-on" : ""}`}>
|
|
208
|
+
{megaToast != null && (
|
|
209
|
+
<div className="mega-cache-toast show">{megaToast}</div>
|
|
210
|
+
)}
|
|
211
|
+
|
|
212
|
+
{showMega && (
|
|
213
|
+
<div className="mega-cache-banner">
|
|
214
|
+
{"\uD83E\uDD67"} MEGA CACHE! peak {fmtPct(bestMega!)} — first
|
|
215
|
+
reached {fmtDate(firstSeen)}
|
|
216
|
+
</div>
|
|
217
|
+
)}
|
|
218
|
+
|
|
219
|
+
{showMega && (
|
|
220
|
+
<div className="achievement-tile unlocked">
|
|
221
|
+
{"\uD83C\uDFC6"} Opie's Wild Ride
|
|
222
|
+
<span className="ach-detail">
|
|
223
|
+
best {fmtPct(bestMega!)} · first {fmtDate(firstSeen)}
|
|
224
|
+
</span>
|
|
225
|
+
</div>
|
|
226
|
+
)}
|
|
227
|
+
|
|
228
|
+
<h2>High Scores</h2>
|
|
229
|
+
|
|
230
|
+
{!hasData && !loading && (
|
|
231
|
+
<div className="game-empty">
|
|
232
|
+
No scores yet — run a session with game mode on.
|
|
233
|
+
</div>
|
|
234
|
+
)}
|
|
235
|
+
|
|
236
|
+
{hasData && (
|
|
237
|
+
<div className="game-leaderboards">
|
|
238
|
+
<LeaderboardCard
|
|
239
|
+
title="Cache %"
|
|
240
|
+
rows={scores.cache ?? []}
|
|
241
|
+
renderValue={(r) => fmtPct(r.value)}
|
|
242
|
+
/>
|
|
243
|
+
<LeaderboardCard
|
|
244
|
+
title="Dedupe (collapsed)"
|
|
245
|
+
rows={scores.dedupe ?? []}
|
|
246
|
+
renderValue={(r) => r.value.toLocaleString()}
|
|
247
|
+
/>
|
|
248
|
+
<LeaderboardCard
|
|
249
|
+
title="Turns"
|
|
250
|
+
rows={turnsRows}
|
|
251
|
+
badge={
|
|
252
|
+
turnsRows.length > 0 ? (
|
|
253
|
+
<span className="lvl-badge">LVL {lvl}</span>
|
|
254
|
+
) : undefined
|
|
255
|
+
}
|
|
256
|
+
renderValue={(r) => r.value.toLocaleString()}
|
|
257
|
+
/>
|
|
258
|
+
<LeaderboardCard
|
|
259
|
+
title="MEGA CACHE trophies"
|
|
260
|
+
rows={megaRows}
|
|
261
|
+
renderValue={(r) => (
|
|
262
|
+
<>
|
|
263
|
+
{fmtPct(r.value)}
|
|
264
|
+
<span className="lb-meta">
|
|
265
|
+
{fmtDate(
|
|
266
|
+
trophyMeta(r.meta).firstSeenTs ??
|
|
267
|
+
trophyMeta(r.meta).firstSeen ??
|
|
268
|
+
r.ts,
|
|
269
|
+
)}
|
|
270
|
+
</span>
|
|
271
|
+
</>
|
|
272
|
+
)}
|
|
273
|
+
/>
|
|
274
|
+
</div>
|
|
275
|
+
)}
|
|
276
|
+
|
|
277
|
+
{reposCount > 0 && (
|
|
278
|
+
<div className="repos-badge-line">
|
|
279
|
+
<span className="repos-badge">{reposCount} repos</span>
|
|
280
|
+
</div>
|
|
281
|
+
)}
|
|
282
|
+
|
|
283
|
+
<h3>Achievements</h3>
|
|
284
|
+
<AchievementTiles achievements={achievements} />
|
|
285
|
+
</div>
|
|
286
|
+
);
|
|
287
|
+
}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* dashboard-client/src/tabs/MetricsTab.tsx — Metrics tab (C2).
|
|
3
3
|
*
|
|
4
4
|
* Fetches /api/perf (rolling-window aggregates) + /api/snapshot (for model).
|
|
5
|
-
* Renders
|
|
5
|
+
* Renders ModelBadge + PerfChart + 5 perf cards (latency, throughput, process,
|
|
6
|
+
* snapshot cost, TUI lag proxy). Polls every 10s.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import type React from "react";
|
|
@@ -11,6 +12,7 @@ import { useApi } from "../hooks/useApi";
|
|
|
11
12
|
import { fetchPerf, fetchSnapshot } from "../api/client";
|
|
12
13
|
import type { PerfResponse, SnapshotResponse } from "@contracts";
|
|
13
14
|
import { PerfChart } from "../components/PerfChart";
|
|
15
|
+
import { PerfCards } from "../components/PerfCards";
|
|
14
16
|
import { ModelBadge } from "../components/ModelBadge";
|
|
15
17
|
|
|
16
18
|
export default function MetricsTab(): React.ReactElement {
|
|
@@ -44,6 +46,10 @@ export default function MetricsTab(): React.ReactElement {
|
|
|
44
46
|
/>
|
|
45
47
|
)}
|
|
46
48
|
<PerfChart perf={perf} />
|
|
49
|
+
<PerfCards perf={perf} />
|
|
50
|
+
<div className="perf-footer">
|
|
51
|
+
{perf.sampleCount} samples · updated {perf.updatedAt}
|
|
52
|
+
</div>
|
|
47
53
|
</div>
|
|
48
54
|
);
|
|
49
55
|
}
|
|
@@ -1,16 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dashboard-client/src/tabs/OverviewTab.tsx — Overview tab (
|
|
2
|
+
* dashboard-client/src/tabs/OverviewTab.tsx — Overview tab (full spec).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Renders all 11 cards from the spec:
|
|
5
|
+
* 1. Context Window (ContextGauge)
|
|
6
|
+
* 2. Trigger Status (TriggerStatus)
|
|
7
|
+
* 3. Vector Store (session) (VectorStoreCard)
|
|
8
|
+
* 4. Repo (all sessions) (RepoAllSessionsCard)
|
|
9
|
+
* 5. Data Safety (DataSafetyCard)
|
|
10
|
+
* 6. Configuration (ConfigSummaryCard)
|
|
11
|
+
* 7. Model & Cost Savings (CacheStatusPerModel)
|
|
12
|
+
* 8. Crew / Agents (SessionInfo)
|
|
13
|
+
* 9. Cache Hits & Compactions (CacheHitsCard)
|
|
14
|
+
* 10. Time Saved (TimeSavedCard)
|
|
15
|
+
* 11. Legend (LegendCard)
|
|
16
|
+
*
|
|
17
|
+
* All data comes from the snapshot prop passed by App.tsx.
|
|
6
18
|
*/
|
|
7
19
|
|
|
8
20
|
import type React from "react";
|
|
9
21
|
import type { SnapshotResponse } from "@contracts";
|
|
22
|
+
import type { RuntimeSnapshot } from "../utils/types";
|
|
10
23
|
import { ContextGauge } from "../components/ContextGauge";
|
|
11
|
-
import { CompressionCard } from "../components/CompressionCard";
|
|
12
24
|
import { TriggerStatus } from "../components/TriggerStatus";
|
|
25
|
+
import { VectorStoreCard } from "../components/VectorStoreCard";
|
|
26
|
+
import { RepoAllSessionsCard } from "../components/RepoAllSessionsCard";
|
|
27
|
+
import { DataSafetyCard } from "../components/DataSafetyCard";
|
|
28
|
+
import { ConfigSummaryCard } from "../components/ConfigSummaryCard";
|
|
29
|
+
import { CacheStatusPerModel } from "../components/CacheStatusPerModel";
|
|
13
30
|
import { SessionInfo } from "../components/SessionInfo";
|
|
31
|
+
import { CacheHitsCard } from "../components/CacheHitsCard";
|
|
32
|
+
import { TimeSavedCard } from "../components/TimeSavedCard";
|
|
33
|
+
import { LegendCard } from "../components/LegendCard";
|
|
14
34
|
|
|
15
35
|
export interface OverviewTabProps {
|
|
16
36
|
snapshot: SnapshotResponse | null;
|
|
@@ -38,8 +58,18 @@ export default function OverviewTab({
|
|
|
38
58
|
return <div className="tab-stub">Error: {error.message}</div>;
|
|
39
59
|
if (!snapshot) return <div className="tab-stub">No snapshot data.</div>;
|
|
40
60
|
|
|
61
|
+
// Cast to RuntimeSnapshot for tierPct (runtime-only field).
|
|
62
|
+
const d = snapshot as RuntimeSnapshot;
|
|
41
63
|
const { context, compression, trigger, session, store, tier, updatedAt } =
|
|
42
|
-
|
|
64
|
+
d;
|
|
65
|
+
const cfg = d.config;
|
|
66
|
+
const crew = d.crew;
|
|
67
|
+
const integrity = d.integrity;
|
|
68
|
+
const cacheHits = d.cacheHits;
|
|
69
|
+
const compacts = d.compacts;
|
|
70
|
+
const timeSaved = d.timeSaved;
|
|
71
|
+
const repo = d.repo;
|
|
72
|
+
const model = d.model;
|
|
43
73
|
|
|
44
74
|
return (
|
|
45
75
|
<div className="overview-tab">
|
|
@@ -47,19 +77,12 @@ export default function OverviewTab({
|
|
|
47
77
|
<span className="tier-pill">{tier}</span>
|
|
48
78
|
<span className="updated">updated {formatUpdatedAt(updatedAt)}</span>
|
|
49
79
|
</div>
|
|
50
|
-
<div className="card-grid">
|
|
80
|
+
<div className="card-grid overview-card-grid">
|
|
51
81
|
<ContextGauge
|
|
52
82
|
tokens={context.tokens}
|
|
53
83
|
percent={context.percent}
|
|
54
84
|
contextWindow={context.contextWindow}
|
|
55
85
|
/>
|
|
56
|
-
<CompressionCard
|
|
57
|
-
tokensIn={compression.repo.tokensIn}
|
|
58
|
-
tokensOut={compression.repo.tokensOut}
|
|
59
|
-
tokensFreed={compression.repo.tokensFreed}
|
|
60
|
-
compressionPct={compression.repo.compressionPct}
|
|
61
|
-
dedupPct={compression.repo.dedupPct}
|
|
62
|
-
/>
|
|
63
86
|
<TriggerStatus
|
|
64
87
|
armed={trigger.armed}
|
|
65
88
|
ready={trigger.ready}
|
|
@@ -67,13 +90,76 @@ export default function OverviewTab({
|
|
|
67
90
|
thresholdTokens={trigger.thresholdTokens}
|
|
68
91
|
fastGatePct={trigger.fastGatePct}
|
|
69
92
|
/>
|
|
70
|
-
<
|
|
71
|
-
state={session.state}
|
|
72
|
-
persistedThisSession={session.persistedThisSession}
|
|
93
|
+
<VectorStoreCard
|
|
73
94
|
checkpointCount={store.checkpointCount}
|
|
74
|
-
|
|
95
|
+
tokensIn={compression.session.tokensIn}
|
|
96
|
+
tokensOut={compression.session.tokensOut}
|
|
97
|
+
tokensFreed={compression.session.tokensFreed}
|
|
98
|
+
injectedCount={store.injectedCount}
|
|
75
99
|
dedupHitRate={store.dedupHitRate}
|
|
100
|
+
storageDedupRate={store.storageDedupRate}
|
|
101
|
+
dedupCollapsed={store.dedupCollapsed}
|
|
102
|
+
lastCheckpointId={session.lastCheckpointId}
|
|
103
|
+
compressionPct={compression.session.compressionPct}
|
|
104
|
+
dedupPct={compression.session.dedupPct}
|
|
105
|
+
/>
|
|
106
|
+
<RepoAllSessionsCard
|
|
107
|
+
checkpointCount={repo.checkpointCount}
|
|
108
|
+
tokensIn={compression.repo.tokensIn}
|
|
109
|
+
tokensOut={compression.repo.tokensOut}
|
|
110
|
+
tokensFreed={compression.repo.tokensFreed}
|
|
111
|
+
sessionCount={repo.sessionCount}
|
|
112
|
+
dedupCollapsed={repo.dedupCollapsed}
|
|
113
|
+
storageDedupRate={repo.storageDedupRate}
|
|
114
|
+
compressionPct={compression.repo.compressionPct}
|
|
115
|
+
dedupPct={compression.repo.dedupPct}
|
|
116
|
+
/>
|
|
117
|
+
<DataSafetyCard
|
|
118
|
+
regionsRetained={integrity.regionsRetained}
|
|
119
|
+
compressedOriginalBytes={integrity.compressedOriginalBytes}
|
|
120
|
+
duplicatesCollapsed={integrity.duplicatesCollapsed}
|
|
121
|
+
bytesPermanentlyDeleted={integrity.bytesPermanentlyDeleted}
|
|
122
|
+
/>
|
|
123
|
+
<ConfigSummaryCard
|
|
124
|
+
tier={tier}
|
|
125
|
+
presetTier={d.presetTier}
|
|
126
|
+
pressure={d.pressure}
|
|
127
|
+
thresholdTokens={cfg.thresholdTokens}
|
|
128
|
+
tierPct={cfg.tierPct}
|
|
129
|
+
contextWindow={context.contextWindow}
|
|
130
|
+
fastGatePct={cfg.fastGatePct}
|
|
131
|
+
auto={cfg.auto}
|
|
132
|
+
anchorUserMessages={cfg.anchorUserMessages}
|
|
133
|
+
/>
|
|
134
|
+
{model && (
|
|
135
|
+
<CacheStatusPerModel
|
|
136
|
+
name={model.name}
|
|
137
|
+
provider={model.providerName || model.provider}
|
|
138
|
+
inputRate={model.inputRate}
|
|
139
|
+
outputRate={model.outputRate}
|
|
140
|
+
repoTokensFreed={compression.repo.tokensFreed}
|
|
141
|
+
contextWindow={context.contextWindow}
|
|
142
|
+
/>
|
|
143
|
+
)}
|
|
144
|
+
<SessionInfo
|
|
145
|
+
activeAgents={crew.activeAgents}
|
|
146
|
+
currentTurn={crew.currentTurn}
|
|
147
|
+
/>
|
|
148
|
+
<CacheHitsCard
|
|
149
|
+
cacheHitsSession={cacheHits.session}
|
|
150
|
+
cacheHitsTotal={cacheHits.total}
|
|
151
|
+
tokensSavedSession={cacheHits.sessionTokensSaved}
|
|
152
|
+
tokensSavedTotal={cacheHits.totalTokensSaved}
|
|
153
|
+
compactionsSession={compacts.session}
|
|
154
|
+
compactionsTotal={compacts.total}
|
|
155
|
+
/>
|
|
156
|
+
<TimeSavedCard
|
|
157
|
+
compactSessionSec={timeSaved.compact.sessionSec}
|
|
158
|
+
compactTotalSec={timeSaved.compact.totalSec}
|
|
159
|
+
cacheHitSessionSec={timeSaved.cacheHit.sessionSec}
|
|
160
|
+
cacheHitTotalSec={timeSaved.cacheHit.totalSec}
|
|
76
161
|
/>
|
|
162
|
+
<LegendCard />
|
|
77
163
|
</div>
|
|
78
164
|
</div>
|
|
79
165
|
);
|
|
@@ -1,56 +1,91 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dashboard-client/src/tabs/ReposTab.tsx — Repos tab (C2).
|
|
2
|
+
* dashboard-client/src/tabs/ReposTab.tsx — Repos tab (C2 fleshed out).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Renders: SummaryTiles (4 tiles), All Repositories table (RepoTable),
|
|
5
|
+
* Active Repos table (ActiveReposTable), Savings by Model table
|
|
6
|
+
* (SavingsByModelTable), and per-repo detail modal (RepoDetailModal).
|
|
7
|
+
*
|
|
8
|
+
* Data sources: fetchIndex() for summary + all repos + savings-by-model;
|
|
9
|
+
* fetchServers() for active repos. Both polled every 10s.
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
12
|
import type React from "react";
|
|
9
13
|
import { useCallback, useState } from "react";
|
|
10
14
|
import { useApi } from "../hooks/useApi";
|
|
11
|
-
import {
|
|
12
|
-
import type {
|
|
13
|
-
ReposResponse,
|
|
14
|
-
SummaryResponse,
|
|
15
|
-
IndexesIndexRow,
|
|
16
|
-
} from "@contracts";
|
|
15
|
+
import { fetchIndex, fetchServers } from "../api/client";
|
|
16
|
+
import type { IndexesIndexRow, ServersResponse } from "@contracts";
|
|
17
17
|
import { SummaryTiles } from "../components/SummaryTiles";
|
|
18
18
|
import { RepoTable } from "../components/RepoTable";
|
|
19
19
|
import { RepoDetailModal } from "../components/RepoDetailModal";
|
|
20
|
+
import { ActiveReposTable } from "../components/ActiveReposTable";
|
|
21
|
+
import { SavingsByModelTable } from "../components/SavingsByModelTable";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Actual runtime shape of /api/index. The contract's IndexesSummaryResponse
|
|
25
|
+
* puts summary fields at the top level, but the real server nests them under
|
|
26
|
+
* `summary` (matching IndexFallbackResponse shape with non-null summary).
|
|
27
|
+
*/
|
|
28
|
+
interface DashboardIndexSummary {
|
|
29
|
+
totalRepos: number;
|
|
30
|
+
totalCheckpoints: number;
|
|
31
|
+
totalTokensSaved: number;
|
|
32
|
+
totalCompressedOriginalBytes: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface DashboardIndexResponse {
|
|
36
|
+
updatedAt: string | null;
|
|
37
|
+
summary: DashboardIndexSummary | null;
|
|
38
|
+
repos: IndexesIndexRow[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Fetch /api/index and cast to the actual runtime shape. */
|
|
42
|
+
async function fetchIndexTyped(): Promise<DashboardIndexResponse> {
|
|
43
|
+
return (await fetchIndex()) as unknown as DashboardIndexResponse;
|
|
44
|
+
}
|
|
20
45
|
|
|
21
46
|
export default function ReposTab(): React.ReactElement {
|
|
22
47
|
const [selected, setSelected] = useState<IndexesIndexRow | null>(null);
|
|
23
48
|
|
|
24
|
-
const { data:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
49
|
+
const { data: indexData, error: indexErr } =
|
|
50
|
+
useApi<DashboardIndexResponse>(
|
|
51
|
+
useCallback(() => fetchIndexTyped(), []),
|
|
52
|
+
{ pollInterval: 10_000 },
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const { data: serversData } = useApi<ServersResponse>(
|
|
56
|
+
useCallback(() => fetchServers(), []),
|
|
30
57
|
{ pollInterval: 10_000 },
|
|
31
58
|
);
|
|
32
59
|
|
|
33
|
-
if (
|
|
60
|
+
if (indexErr && !indexData) {
|
|
34
61
|
return (
|
|
35
|
-
<div className="tab-stub">Error loading repos: {
|
|
62
|
+
<div className="tab-stub">Error loading repos: {indexErr.message}</div>
|
|
36
63
|
);
|
|
37
64
|
}
|
|
38
|
-
if (!
|
|
65
|
+
if (!indexData) {
|
|
39
66
|
return <div className="tab-stub">Loading repos…</div>;
|
|
40
67
|
}
|
|
41
68
|
|
|
42
|
-
const
|
|
43
|
-
const
|
|
69
|
+
const summary = indexData.summary;
|
|
70
|
+
const repos = indexData.repos;
|
|
71
|
+
const servers = serversData?.servers ?? [];
|
|
44
72
|
|
|
45
73
|
return (
|
|
46
74
|
<div className="repos-tab">
|
|
47
75
|
<SummaryTiles
|
|
48
|
-
totalRepos={
|
|
49
|
-
activeRepos={summaryResp?.activeRepos ?? 0}
|
|
76
|
+
totalRepos={summary?.totalRepos ?? 0}
|
|
50
77
|
totalCheckpoints={summary?.totalCheckpoints ?? 0}
|
|
51
78
|
totalTokensSaved={summary?.totalTokensSaved ?? 0}
|
|
79
|
+
compressedOriginalBytes={summary?.totalCompressedOriginalBytes ?? 0}
|
|
52
80
|
/>
|
|
81
|
+
<h2 className="section-header">All Repositories</h2>
|
|
53
82
|
<RepoTable repos={repos} onSelect={setSelected} />
|
|
83
|
+
<h2 className="section-header">
|
|
84
|
+
Active Repos — Live Cache Hits & Compactions
|
|
85
|
+
</h2>
|
|
86
|
+
<ActiveReposTable servers={servers} />
|
|
87
|
+
<h2 className="section-header">Savings by Model</h2>
|
|
88
|
+
<SavingsByModelTable repos={repos} />
|
|
54
89
|
{selected && (
|
|
55
90
|
<RepoDetailModal repo={selected} onClose={() => setSelected(null)} />
|
|
56
91
|
)}
|