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
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>mega-compact dashboard</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-DMntLu-8.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="./assets/index-Cfxniu2R.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
|
@@ -19,10 +19,19 @@ const ReposTab = React.lazy(() => import("./tabs/ReposTab"));
|
|
|
19
19
|
const EventsTab = React.lazy(() => import("./tabs/EventsTab"));
|
|
20
20
|
const ConfigTab = React.lazy(() => import("./tabs/ConfigTab"));
|
|
21
21
|
const MetricsTab = React.lazy(() => import("./tabs/MetricsTab"));
|
|
22
|
+
const CacheTab = React.lazy(() => import("./tabs/CacheTab"));
|
|
23
|
+
const GameTab = React.lazy(() => import("./tabs/GameTab"));
|
|
24
|
+
const AchievementsTab = React.lazy(() => import("./tabs/AchievementsTab"));
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
export type TabId =
|
|
27
|
+
| "overview"
|
|
28
|
+
| "repos"
|
|
29
|
+
| "events"
|
|
30
|
+
| "config"
|
|
31
|
+
| "metrics"
|
|
32
|
+
| "cache"
|
|
33
|
+
| "game"
|
|
34
|
+
| "achievements";
|
|
26
35
|
|
|
27
36
|
const TABS: Array<{ id: TabId; label: string }> = [
|
|
28
37
|
{ id: "overview", label: "Overview" },
|
|
@@ -30,6 +39,9 @@ const TABS: Array<{ id: TabId; label: string }> = [
|
|
|
30
39
|
{ id: "events", label: "Events" },
|
|
31
40
|
{ id: "config", label: "Config" },
|
|
32
41
|
{ id: "metrics", label: "Metrics" },
|
|
42
|
+
{ id: "cache", label: "Cache" },
|
|
43
|
+
{ id: "game", label: "Game" },
|
|
44
|
+
{ id: "achievements", label: "Achievements" },
|
|
33
45
|
];
|
|
34
46
|
|
|
35
47
|
export default function App(): React.ReactElement {
|
|
@@ -73,7 +85,9 @@ export default function App(): React.ReactElement {
|
|
|
73
85
|
{activeTab === "events" && <EventsTab />}
|
|
74
86
|
{activeTab === "config" && <ConfigTab />}
|
|
75
87
|
{activeTab === "metrics" && <MetricsTab />}
|
|
76
|
-
{
|
|
88
|
+
{activeTab === "cache" && <CacheTab />}
|
|
89
|
+
{activeTab === "game" && <GameTab />}
|
|
90
|
+
{activeTab === "achievements" && <AchievementsTab />}
|
|
77
91
|
</React.Suspense>
|
|
78
92
|
</main>
|
|
79
93
|
</div>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/components/AchievementTiles.tsx — shared achievement grid.
|
|
3
|
+
*
|
|
4
|
+
* Renders a grid of achievement tiles from fetchAchievements() data.
|
|
5
|
+
* Tile states: unlocked (icon+title+date), visible-locked (??? teaser),
|
|
6
|
+
* hidden+locked (not rendered). Just-unlocked tiles get a pulse animation.
|
|
7
|
+
* Toast notification fires for newly-unlocked achievements.
|
|
8
|
+
*
|
|
9
|
+
* Used by both GameTab (sub-section) and AchievementsTab (standalone).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type React from "react";
|
|
13
|
+
import { useRef, useState, useEffect } from "react";
|
|
14
|
+
import type { AchievementRow } from "@contracts";
|
|
15
|
+
|
|
16
|
+
export interface AchievementTilesProps {
|
|
17
|
+
/** Achievement rows from /api/achievements. */
|
|
18
|
+
achievements: AchievementRow[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Format unlock timestamp (seconds → locale string). */
|
|
22
|
+
function fmtUnlockDate(ts: number): string {
|
|
23
|
+
try {
|
|
24
|
+
return new Date(ts * 1000).toLocaleString();
|
|
25
|
+
} catch {
|
|
26
|
+
return "\u2014";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function AchievementTiles({
|
|
31
|
+
achievements,
|
|
32
|
+
}: AchievementTilesProps): React.ReactElement {
|
|
33
|
+
const lastMaxTsRef = useRef(0);
|
|
34
|
+
const [toastText, setToastText] = useState<string | null>(null);
|
|
35
|
+
|
|
36
|
+
const maxTs = achievements.reduce(
|
|
37
|
+
(mx, a) => Math.max(mx, a.unlocked_at ?? 0),
|
|
38
|
+
0,
|
|
39
|
+
);
|
|
40
|
+
const prevMaxTs = lastMaxTsRef.current;
|
|
41
|
+
|
|
42
|
+
/* Update ref after render so next poll sees previous baseline. */
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (maxTs > lastMaxTsRef.current) {
|
|
45
|
+
lastMaxTsRef.current = maxTs;
|
|
46
|
+
}
|
|
47
|
+
}, [maxTs]);
|
|
48
|
+
|
|
49
|
+
/* Toast for newly-unlocked achievements. */
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (prevMaxTs > 0 && maxTs > prevMaxTs) {
|
|
52
|
+
const newly = achievements.filter(
|
|
53
|
+
(a) => a.unlocked_at != null && (a.unlocked_at ?? 0) > prevMaxTs,
|
|
54
|
+
);
|
|
55
|
+
if (newly.length > 0) {
|
|
56
|
+
const text =
|
|
57
|
+
newly
|
|
58
|
+
.map((a) => `${a.icon ?? ""} ${a.title}`.trim())
|
|
59
|
+
.join(", ") + " unlocked!";
|
|
60
|
+
setToastText(text);
|
|
61
|
+
const t = setTimeout(() => setToastText(null), 4000);
|
|
62
|
+
return (): void => clearTimeout(t);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}, [achievements, maxTs, prevMaxTs]);
|
|
66
|
+
|
|
67
|
+
/* Visible tiles: skip hidden+locked (hidden === 1 && unlocked_at == null). */
|
|
68
|
+
const tiles = achievements.filter(
|
|
69
|
+
(a) => !(a.hidden === 1 && a.unlocked_at == null),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div className="ach-section">
|
|
74
|
+
{toastText != null && (
|
|
75
|
+
<div className="ach-toast show">{toastText}</div>
|
|
76
|
+
)}
|
|
77
|
+
<div className="ach-tiles">
|
|
78
|
+
{tiles.length === 0 && (
|
|
79
|
+
<span className="repo-none">no achievements yet</span>
|
|
80
|
+
)}
|
|
81
|
+
{tiles.map((a) => {
|
|
82
|
+
if (a.unlocked_at != null) {
|
|
83
|
+
const isNew = prevMaxTs > 0 && (a.unlocked_at ?? 0) > prevMaxTs;
|
|
84
|
+
return (
|
|
85
|
+
<div
|
|
86
|
+
key={a.id}
|
|
87
|
+
className={`ach-tile unlocked${isNew ? " just-unlocked" : ""}`}
|
|
88
|
+
>
|
|
89
|
+
{a.icon ?? ""} {a.title}
|
|
90
|
+
<span className="ach-detail">
|
|
91
|
+
unlocked {fmtUnlockDate(a.unlocked_at)}
|
|
92
|
+
</span>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
return (
|
|
97
|
+
<div key={a.id} className="ach-tile locked">
|
|
98
|
+
??? {a.title}
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
})}
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/components/ActiveReposTable.tsx — Active repos table.
|
|
3
|
+
*
|
|
4
|
+
* 9 columns: Repo, Model, Tier, Context %, State, Compactions s/t,
|
|
5
|
+
* Cache Hits s/t, Compact s/t, CacheHit s/t.
|
|
6
|
+
* Data from fetchServers() via useApi with 10s polling.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type React from "react";
|
|
10
|
+
import type { ServerEntry } from "@contracts";
|
|
11
|
+
|
|
12
|
+
export interface ActiveReposTableProps {
|
|
13
|
+
servers: ServerEntry[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Format seconds → human-readable duration (matches html.ts fmtSec). */
|
|
17
|
+
function fmtSec(s: number | undefined): string {
|
|
18
|
+
const v = s ?? 0;
|
|
19
|
+
if (v >= 3600) return `${(v / 3600).toFixed(1)}h`;
|
|
20
|
+
if (v >= 60) return `${Math.round(v / 60)}m`;
|
|
21
|
+
if (v >= 1) return `${v.toFixed(1)}s`;
|
|
22
|
+
return `${Math.round(v * 1000)}ms`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const HEADERS = [
|
|
26
|
+
"Repo",
|
|
27
|
+
"Model",
|
|
28
|
+
"Tier",
|
|
29
|
+
"Context %",
|
|
30
|
+
"State",
|
|
31
|
+
"Compactions (s/t)",
|
|
32
|
+
"Cache Hits (s/t)",
|
|
33
|
+
"Compact s/t (s)",
|
|
34
|
+
"CacheHit s/t (s)",
|
|
35
|
+
] as const;
|
|
36
|
+
|
|
37
|
+
export function ActiveReposTable({
|
|
38
|
+
servers,
|
|
39
|
+
}: ActiveReposTableProps): React.ReactElement {
|
|
40
|
+
return (
|
|
41
|
+
<div className="active-repos-section">
|
|
42
|
+
<p className="legend-note">
|
|
43
|
+
Repos seen within the last 30 minutes, with their per-repo
|
|
44
|
+
cache-hit, compaction, and time-saved (est.) totals pulled live
|
|
45
|
+
from each repo's dashboard.json.
|
|
46
|
+
</p>
|
|
47
|
+
<div className="table-scroll">
|
|
48
|
+
<table className="active-repos-table">
|
|
49
|
+
<thead>
|
|
50
|
+
<tr>
|
|
51
|
+
{HEADERS.map((h, i) => (
|
|
52
|
+
<th
|
|
53
|
+
key={h}
|
|
54
|
+
className={i >= 3 && i !== 4 ? "num" : undefined}
|
|
55
|
+
>
|
|
56
|
+
{h}
|
|
57
|
+
</th>
|
|
58
|
+
))}
|
|
59
|
+
</tr>
|
|
60
|
+
</thead>
|
|
61
|
+
<tbody>
|
|
62
|
+
{servers.length === 0 && (
|
|
63
|
+
<tr>
|
|
64
|
+
<td colSpan={9} className="repo-empty">
|
|
65
|
+
No active repositories.
|
|
66
|
+
</td>
|
|
67
|
+
</tr>
|
|
68
|
+
)}
|
|
69
|
+
{servers.map((r) => {
|
|
70
|
+
const ch = r.cacheHits ?? {
|
|
71
|
+
session: 0,
|
|
72
|
+
total: 0,
|
|
73
|
+
sessionTokensSaved: 0,
|
|
74
|
+
totalTokensSaved: 0,
|
|
75
|
+
};
|
|
76
|
+
const cp = r.compacts ?? { session: 0, total: 0 };
|
|
77
|
+
const ts = r.timeSaved ?? {
|
|
78
|
+
compact: { sessionSec: 0, totalSec: 0 },
|
|
79
|
+
cacheHit: { sessionSec: 0, totalSec: 0 },
|
|
80
|
+
};
|
|
81
|
+
return (
|
|
82
|
+
<tr key={r.repoRoot}>
|
|
83
|
+
<td title={r.repoRoot}>
|
|
84
|
+
{r.displayName || r.repoRoot}
|
|
85
|
+
</td>
|
|
86
|
+
<td>{r.model ?? "\u2014"}</td>
|
|
87
|
+
<td>{r.tier ?? "\u2014"}</td>
|
|
88
|
+
<td className="num">
|
|
89
|
+
{r.contextPct != null
|
|
90
|
+
? `${Math.round(r.contextPct * 100)}%`
|
|
91
|
+
: "\u2014"}
|
|
92
|
+
</td>
|
|
93
|
+
<td>{r.state ?? "\u2014"}</td>
|
|
94
|
+
<td className="num">
|
|
95
|
+
{cp.session} / {cp.total}
|
|
96
|
+
</td>
|
|
97
|
+
<td className="num">
|
|
98
|
+
{ch.session} / {ch.total}
|
|
99
|
+
</td>
|
|
100
|
+
<td className="num">
|
|
101
|
+
{fmtSec(ts.compact.sessionSec)} /{" "}
|
|
102
|
+
{fmtSec(ts.compact.totalSec)}
|
|
103
|
+
</td>
|
|
104
|
+
<td className="num">
|
|
105
|
+
{fmtSec(ts.cacheHit.sessionSec)} /{" "}
|
|
106
|
+
{fmtSec(ts.cacheHit.totalSec)}
|
|
107
|
+
</td>
|
|
108
|
+
</tr>
|
|
109
|
+
);
|
|
110
|
+
})}
|
|
111
|
+
</tbody>
|
|
112
|
+
</table>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/components/CacheHitsCard.tsx — Cache Hits & Compactions card.
|
|
3
|
+
*
|
|
4
|
+
* 6 fields: Cache Hits (session), Cache Hits (total),
|
|
5
|
+
* Tokens Saved (session), Tokens Saved (total),
|
|
6
|
+
* Compactions (session), Compactions (total).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type React from "react";
|
|
10
|
+
|
|
11
|
+
export interface CacheHitsCardProps {
|
|
12
|
+
/** Cache hits in the current session. */
|
|
13
|
+
cacheHitsSession: number;
|
|
14
|
+
/** Total cache hits across all sessions. */
|
|
15
|
+
cacheHitsTotal: number;
|
|
16
|
+
/** Tokens saved by cache hits this session. */
|
|
17
|
+
tokensSavedSession: number;
|
|
18
|
+
/** Total tokens saved by cache hits. */
|
|
19
|
+
tokensSavedTotal: number;
|
|
20
|
+
/** Compactions performed this session. */
|
|
21
|
+
compactionsSession: number;
|
|
22
|
+
/** Total compactions performed. */
|
|
23
|
+
compactionsTotal: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function StatRow({
|
|
27
|
+
label,
|
|
28
|
+
value,
|
|
29
|
+
}: {
|
|
30
|
+
label: string;
|
|
31
|
+
value: string | number;
|
|
32
|
+
}): React.ReactElement {
|
|
33
|
+
return (
|
|
34
|
+
<div className="ov-stat-row">
|
|
35
|
+
<span className="ov-stat-label">{label}</span>
|
|
36
|
+
<span className="ov-stat-value">{value}</span>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function CacheHitsCard(
|
|
42
|
+
props: CacheHitsCardProps,
|
|
43
|
+
): React.ReactElement {
|
|
44
|
+
return (
|
|
45
|
+
<div className="card cache-hits-card">
|
|
46
|
+
<h3>💾 Cache Hits & Compactions</h3>
|
|
47
|
+
<StatRow
|
|
48
|
+
label="Cache Hits (session)"
|
|
49
|
+
value={props.cacheHitsSession.toLocaleString()}
|
|
50
|
+
/>
|
|
51
|
+
<StatRow
|
|
52
|
+
label="Cache Hits (total)"
|
|
53
|
+
value={props.cacheHitsTotal.toLocaleString()}
|
|
54
|
+
/>
|
|
55
|
+
<StatRow
|
|
56
|
+
label="Tokens Saved (session)"
|
|
57
|
+
value={props.tokensSavedSession.toLocaleString()}
|
|
58
|
+
/>
|
|
59
|
+
<StatRow
|
|
60
|
+
label="Tokens Saved (total)"
|
|
61
|
+
value={props.tokensSavedTotal.toLocaleString()}
|
|
62
|
+
/>
|
|
63
|
+
<StatRow
|
|
64
|
+
label="Compactions (session)"
|
|
65
|
+
value={props.compactionsSession.toLocaleString()}
|
|
66
|
+
/>
|
|
67
|
+
<StatRow
|
|
68
|
+
label="Compactions (total)"
|
|
69
|
+
value={props.compactionsTotal.toLocaleString()}
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -1,44 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/components/CacheStatusPerModel.tsx — Model & Cost Savings card.
|
|
3
|
+
*
|
|
4
|
+
* Shows $ saved, context-windows extended, Model, Provider, Input Rate, Output Rate.
|
|
5
|
+
* Tooltips copied verbatim from html.ts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
1
8
|
import type React from "react";
|
|
2
|
-
|
|
3
|
-
|
|
9
|
+
|
|
10
|
+
export interface ModelCostCardProps {
|
|
11
|
+
/** Model name. */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Provider display name (providerName fallback to provider). */
|
|
4
14
|
provider: string;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
15
|
+
/** USD per input token. */
|
|
16
|
+
inputRate: number;
|
|
17
|
+
/** USD per output token. */
|
|
18
|
+
outputRate: number;
|
|
19
|
+
/** Tokens freed (repo-level) used for cost calc. */
|
|
20
|
+
repoTokensFreed: number;
|
|
21
|
+
/** Context window size used for windows calc. */
|
|
22
|
+
contextWindow: number;
|
|
10
23
|
}
|
|
11
|
-
|
|
12
|
-
|
|
24
|
+
|
|
25
|
+
function StatRow({
|
|
26
|
+
label,
|
|
27
|
+
value,
|
|
28
|
+
title,
|
|
13
29
|
}: {
|
|
14
|
-
|
|
30
|
+
label: string;
|
|
31
|
+
value: string;
|
|
32
|
+
title?: string;
|
|
15
33
|
}): React.ReactElement {
|
|
16
34
|
return (
|
|
17
|
-
<div className="
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
<div className="ov-stat-row">
|
|
36
|
+
<span className="ov-stat-label" title={title}>
|
|
37
|
+
{label}
|
|
38
|
+
</span>
|
|
39
|
+
<span className="ov-stat-value">{value}</span>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function CacheStatusPerModel(
|
|
45
|
+
props: ModelCostCardProps,
|
|
46
|
+
): React.ReactElement {
|
|
47
|
+
const repoSaved = props.repoTokensFreed || 0;
|
|
48
|
+
let usd = 0;
|
|
49
|
+
let windows = "0";
|
|
50
|
+
|
|
51
|
+
if (props.inputRate && repoSaved > 0) {
|
|
52
|
+
usd = repoSaved * props.inputRate;
|
|
53
|
+
const win = props.contextWindow || 0;
|
|
54
|
+
windows = win > 0 ? (repoSaved / win).toFixed(1) : "0";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const usdStr = `≈ $${usd.toFixed(4)} saved`;
|
|
58
|
+
const windowsStr = `${windows} context-windows extended`;
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div className="card model-cost-card">
|
|
62
|
+
<h3>💰 Model & Cost Savings</h3>
|
|
63
|
+
<div className="ov-cost-usd">{usdStr}</div>
|
|
64
|
+
<div className="ov-cost-sub">{windowsStr}</div>
|
|
65
|
+
<StatRow
|
|
66
|
+
label="Model"
|
|
67
|
+
value={props.name}
|
|
68
|
+
title="The model pi is currently using — its pricing drives the cost figure."
|
|
69
|
+
/>
|
|
70
|
+
<StatRow
|
|
71
|
+
label="Provider"
|
|
72
|
+
value={props.provider}
|
|
73
|
+
title="The provider serving the model."
|
|
74
|
+
/>
|
|
75
|
+
<StatRow
|
|
76
|
+
label="Input Rate"
|
|
77
|
+
value={`$${props.inputRate.toFixed(6)}`}
|
|
78
|
+
title="USD per input token, from the model's pricing."
|
|
79
|
+
/>
|
|
80
|
+
<StatRow
|
|
81
|
+
label="Output Rate"
|
|
82
|
+
value={`$${props.outputRate.toFixed(6)}`}
|
|
83
|
+
title="USD per output token, from the model's pricing."
|
|
84
|
+
/>
|
|
42
85
|
</div>
|
|
43
86
|
);
|
|
44
87
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/components/ConfigSummaryCard.tsx — Configuration summary.
|
|
3
|
+
*
|
|
4
|
+
* 7 fields with tooltips (verbatim from html.ts):
|
|
5
|
+
* Tier (live), Preset, Pressure, Threshold, Fast Gate, Auto, Anchor.
|
|
6
|
+
*
|
|
7
|
+
* NOTE: pressure and tierPct are 0–1 fractions at runtime.
|
|
8
|
+
* fastGatePct is 0–100 at runtime.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type React from "react";
|
|
12
|
+
|
|
13
|
+
export interface ConfigSummaryCardProps {
|
|
14
|
+
/** Current tier name. */
|
|
15
|
+
tier: string;
|
|
16
|
+
/** Preset tier name. */
|
|
17
|
+
presetTier: string;
|
|
18
|
+
/** Live pressure — 0–1 fraction. */
|
|
19
|
+
pressure: number;
|
|
20
|
+
/** Token threshold. */
|
|
21
|
+
thresholdTokens: number;
|
|
22
|
+
/** Tier percentage — 0–1 fraction (runtime-only field). */
|
|
23
|
+
tierPct?: number;
|
|
24
|
+
/** Context window size. */
|
|
25
|
+
contextWindow: number;
|
|
26
|
+
/** Fast-gate percentage — 0–100. */
|
|
27
|
+
fastGatePct: number;
|
|
28
|
+
/** Whether auto-compaction is enabled. */
|
|
29
|
+
auto: boolean;
|
|
30
|
+
/** Number of anchor user messages. */
|
|
31
|
+
anchorUserMessages: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function StatRow({
|
|
35
|
+
label,
|
|
36
|
+
value,
|
|
37
|
+
title,
|
|
38
|
+
}: {
|
|
39
|
+
label: string;
|
|
40
|
+
value: string;
|
|
41
|
+
title?: string;
|
|
42
|
+
}): React.ReactElement {
|
|
43
|
+
return (
|
|
44
|
+
<div className="ov-stat-row">
|
|
45
|
+
<span className="ov-stat-label" title={title}>
|
|
46
|
+
{label}
|
|
47
|
+
</span>
|
|
48
|
+
<span className="ov-stat-value">{value}</span>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function ConfigSummaryCard(
|
|
54
|
+
props: ConfigSummaryCardProps,
|
|
55
|
+
): React.ReactElement {
|
|
56
|
+
const pressurePct = `${Math.round((props.pressure ?? 0) * 100)}%`;
|
|
57
|
+
|
|
58
|
+
let thresholdTxt = props.thresholdTokens.toLocaleString();
|
|
59
|
+
if (props.tierPct != null && props.contextWindow > 0) {
|
|
60
|
+
thresholdTxt += ` (${Math.round(props.tierPct * 100)}% of ${props.contextWindow.toLocaleString()})`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div className="card config-summary-card">
|
|
65
|
+
<h3>Configuration</h3>
|
|
66
|
+
<StatRow
|
|
67
|
+
label="Tier"
|
|
68
|
+
value={`${props.tier} (live)`}
|
|
69
|
+
title="Live pressure band — climbs low→mega as context fills the window."
|
|
70
|
+
/>
|
|
71
|
+
<StatRow
|
|
72
|
+
label="Preset"
|
|
73
|
+
value={props.presetTier}
|
|
74
|
+
title="The env-resolved base compaction preset (low/medium/high/ultra/mega) that set the token threshold."
|
|
75
|
+
/>
|
|
76
|
+
<StatRow
|
|
77
|
+
label="Pressure"
|
|
78
|
+
value={pressurePct}
|
|
79
|
+
title="Live pressure = currentTokens / threshold — % of the model context window (threshold fires at the tier's % of window)."
|
|
80
|
+
/>
|
|
81
|
+
<StatRow
|
|
82
|
+
label="Threshold"
|
|
83
|
+
value={thresholdTxt}
|
|
84
|
+
title="Compaction threshold = tierPct × model context window — mega-compact trims BELOW pi's native ~80% auto-compact for any model size."
|
|
85
|
+
/>
|
|
86
|
+
<StatRow
|
|
87
|
+
label="Fast Gate"
|
|
88
|
+
value={`${props.fastGatePct}%`}
|
|
89
|
+
title="Fast-gate arming floor — the live trim arms once context passes this % of the window."
|
|
90
|
+
/>
|
|
91
|
+
<StatRow
|
|
92
|
+
label="Auto"
|
|
93
|
+
value={props.auto ? "enabled" : "disabled"}
|
|
94
|
+
/>
|
|
95
|
+
<StatRow
|
|
96
|
+
label="Anchor"
|
|
97
|
+
value={String(props.anchorUserMessages)}
|
|
98
|
+
/>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dashboard-client/src/components/ContextGauge.tsx — token usage meter.
|
|
3
3
|
*
|
|
4
|
-
* Color-coded percent fill bar: green <
|
|
5
|
-
* Sublabel: "{tokens} / {contextWindow} tokens
|
|
4
|
+
* Color-coded percent fill bar: green <70%, yellow 70–89%, red ≥90%.
|
|
5
|
+
* Sublabel: "{tokens} / {contextWindow} tokens".
|
|
6
|
+
* Shows percentage as "{pct}%" in the header.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import type React from "react";
|
|
@@ -17,33 +18,28 @@ export interface ContextGaugeProps {
|
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
function severityClass(percent: number): string {
|
|
20
|
-
if (percent >=
|
|
21
|
-
if (percent >=
|
|
21
|
+
if (percent >= 90) return "gauge-red";
|
|
22
|
+
if (percent >= 70) return "gauge-yellow";
|
|
22
23
|
return "gauge-green";
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
function formatTokens(n: number): string {
|
|
26
|
-
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
|
|
27
|
-
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
|
|
28
|
-
return String(n);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
26
|
export function ContextGauge({
|
|
32
27
|
tokens,
|
|
33
28
|
percent,
|
|
34
29
|
contextWindow,
|
|
35
30
|
}: ContextGaugeProps): React.ReactElement {
|
|
36
31
|
const pct = percent ?? 0;
|
|
37
|
-
const fillWidth = Math.max(
|
|
32
|
+
const fillWidth = Math.max(pct, 1);
|
|
38
33
|
const cls = severityClass(pct);
|
|
39
|
-
const
|
|
34
|
+
const tokStr =
|
|
40
35
|
tokens !== null && tokens !== undefined
|
|
41
|
-
?
|
|
42
|
-
:
|
|
36
|
+
? tokens.toLocaleString()
|
|
37
|
+
: "?";
|
|
38
|
+
const label = `${tokStr} / ${contextWindow.toLocaleString()} tokens`;
|
|
43
39
|
|
|
44
40
|
return (
|
|
45
41
|
<div className="card context-gauge">
|
|
46
|
-
<h3>Context
|
|
42
|
+
<h3>Context Window — {pct}%</h3>
|
|
47
43
|
<div
|
|
48
44
|
className={`gauge-bar ${cls}`}
|
|
49
45
|
role="meter"
|