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.
Files changed (76) hide show
  1. package/extensions/dashboard-client/dist/assets/AchievementTiles-BH26e95w.js +2 -0
  2. package/extensions/dashboard-client/dist/assets/AchievementTiles-BH26e95w.js.map +1 -0
  3. package/extensions/dashboard-client/dist/assets/AchievementsTab-B94wvRV_.js +2 -0
  4. package/extensions/dashboard-client/dist/assets/AchievementsTab-B94wvRV_.js.map +1 -0
  5. package/extensions/dashboard-client/dist/assets/CacheTab-DPmw3NHR.js +2 -0
  6. package/extensions/dashboard-client/dist/assets/CacheTab-DPmw3NHR.js.map +1 -0
  7. package/extensions/dashboard-client/dist/assets/ConfigTab-C3g3smRL.js +2 -0
  8. package/extensions/dashboard-client/dist/assets/ConfigTab-C3g3smRL.js.map +1 -0
  9. package/extensions/dashboard-client/dist/assets/EventsTab-B5PicfFw.js +2 -0
  10. package/extensions/dashboard-client/dist/assets/EventsTab-B5PicfFw.js.map +1 -0
  11. package/extensions/dashboard-client/dist/assets/GameTab-CsVwp78a.js +2 -0
  12. package/extensions/dashboard-client/dist/assets/GameTab-CsVwp78a.js.map +1 -0
  13. package/extensions/dashboard-client/dist/assets/MetricsTab-Cf72UewX.js +2 -0
  14. package/extensions/dashboard-client/dist/assets/MetricsTab-Cf72UewX.js.map +1 -0
  15. package/extensions/dashboard-client/dist/assets/OverviewTab-Dh6aG3gy.js +2 -0
  16. package/extensions/dashboard-client/dist/assets/OverviewTab-Dh6aG3gy.js.map +1 -0
  17. package/extensions/dashboard-client/dist/assets/ReposTab-HXf2dorl.js +2 -0
  18. package/extensions/dashboard-client/dist/assets/ReposTab-HXf2dorl.js.map +1 -0
  19. package/extensions/dashboard-client/dist/assets/TimeSavedCard-kUnoospE.js +2 -0
  20. package/extensions/dashboard-client/dist/assets/TimeSavedCard-kUnoospE.js.map +1 -0
  21. package/extensions/dashboard-client/dist/assets/index-Cfxniu2R.css +1 -0
  22. package/extensions/dashboard-client/dist/assets/{index-XAIN8km6.js → index-DMntLu-8.js} +11 -10
  23. package/extensions/dashboard-client/dist/assets/index-DMntLu-8.js.map +1 -0
  24. package/extensions/dashboard-client/dist/index.html +2 -2
  25. package/extensions/dashboard-client/src/App.tsx +18 -4
  26. package/extensions/dashboard-client/src/components/AchievementTiles.tsx +105 -0
  27. package/extensions/dashboard-client/src/components/ActiveReposTable.tsx +116 -0
  28. package/extensions/dashboard-client/src/components/CacheHitsCard.tsx +73 -0
  29. package/extensions/dashboard-client/src/components/CacheStatusPerModel.tsx +78 -35
  30. package/extensions/dashboard-client/src/components/ConfigSummaryCard.tsx +101 -0
  31. package/extensions/dashboard-client/src/components/ContextGauge.tsx +11 -15
  32. package/extensions/dashboard-client/src/components/DataSafetyCard.tsx +42 -33
  33. package/extensions/dashboard-client/src/components/EventCategoryFilter.tsx +46 -9
  34. package/extensions/dashboard-client/src/components/LegendCard.tsx +49 -0
  35. package/extensions/dashboard-client/src/components/ModelBadge.tsx +42 -11
  36. package/extensions/dashboard-client/src/components/PerfCards.tsx +91 -0
  37. package/extensions/dashboard-client/src/components/RepoAllSessionsCard.tsx +102 -0
  38. package/extensions/dashboard-client/src/components/RepoDetailModal.tsx +9 -2
  39. package/extensions/dashboard-client/src/components/RepoTable.tsx +30 -22
  40. package/extensions/dashboard-client/src/components/SavingsByModelTable.tsx +226 -0
  41. package/extensions/dashboard-client/src/components/SessionInfo.tsx +25 -50
  42. package/extensions/dashboard-client/src/components/SummaryTiles.tsx +20 -11
  43. package/extensions/dashboard-client/src/components/TimeSavedCard.tsx +61 -0
  44. package/extensions/dashboard-client/src/components/TriggerStatus.tsx +31 -19
  45. package/extensions/dashboard-client/src/components/VectorStoreCard.tsx +134 -0
  46. package/extensions/dashboard-client/src/main.tsx +5 -0
  47. package/extensions/dashboard-client/src/styles/base.css +76 -9
  48. package/extensions/dashboard-client/src/styles/config.css +149 -0
  49. package/extensions/dashboard-client/src/styles/game-achievements.css +265 -0
  50. package/extensions/dashboard-client/src/styles/metrics-extra.css +69 -0
  51. package/extensions/dashboard-client/src/styles/overview-extra.css +170 -0
  52. package/extensions/dashboard-client/src/styles/repos-extra.css +99 -0
  53. package/extensions/dashboard-client/src/styles/repos-metrics.css +14 -23
  54. package/extensions/dashboard-client/src/tabs/AchievementsTab.tsx +55 -0
  55. package/extensions/dashboard-client/src/tabs/CacheTab.tsx +50 -0
  56. package/extensions/dashboard-client/src/tabs/ConfigTab.tsx +240 -5
  57. package/extensions/dashboard-client/src/tabs/EventsTab.tsx +55 -3
  58. package/extensions/dashboard-client/src/tabs/GameTab.tsx +287 -0
  59. package/extensions/dashboard-client/src/tabs/MetricsTab.tsx +7 -1
  60. package/extensions/dashboard-client/src/tabs/OverviewTab.tsx +103 -17
  61. package/extensions/dashboard-client/src/tabs/ReposTab.tsx +57 -22
  62. package/extensions/dashboard-client/src/utils/format.ts +85 -0
  63. package/extensions/dashboard-client/src/utils/types.ts +27 -0
  64. package/package.json +1 -1
  65. package/extensions/dashboard-client/dist/assets/ConfigTab-RrjXWfvr.js +0 -2
  66. package/extensions/dashboard-client/dist/assets/ConfigTab-RrjXWfvr.js.map +0 -1
  67. package/extensions/dashboard-client/dist/assets/EventsTab-BH_FxJ08.js +0 -2
  68. package/extensions/dashboard-client/dist/assets/EventsTab-BH_FxJ08.js.map +0 -1
  69. package/extensions/dashboard-client/dist/assets/MetricsTab-C-PoFV4p.js +0 -2
  70. package/extensions/dashboard-client/dist/assets/MetricsTab-C-PoFV4p.js.map +0 -1
  71. package/extensions/dashboard-client/dist/assets/OverviewTab-Db-qBqFN.js +0 -2
  72. package/extensions/dashboard-client/dist/assets/OverviewTab-Db-qBqFN.js.map +0 -1
  73. package/extensions/dashboard-client/dist/assets/ReposTab-DhCjJvuj.js +0 -2
  74. package/extensions/dashboard-client/dist/assets/ReposTab-DhCjJvuj.js.map +0 -1
  75. package/extensions/dashboard-client/dist/assets/index-Buer5yxB.css +0 -1
  76. 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-XAIN8km6.js"></script>
8
- <link rel="stylesheet" crossorigin href="./assets/index-Buer5yxB.css">
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
- // SPRINT-C2/C3-REMAINING: GameTab, DiagnosticsPanel lazy imports.
24
-
25
- export type TabId = "overview" | "repos" | "events" | "config" | "metrics";
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
- {/* SPRINT-C2/C3-REMAINING: Game tab, diagnostics panel */}
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&apos;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 &amp; 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
- export interface CacheRow {
3
- model: string;
9
+
10
+ export interface ModelCostCardProps {
11
+ /** Model name. */
12
+ name: string;
13
+ /** Provider display name (providerName fallback to provider). */
4
14
  provider: string;
5
- hits: number;
6
- total: number;
7
- saved: string;
8
- pct: number;
9
- compactions: number;
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
- export function CacheStatusPerModel({
12
- rows,
24
+
25
+ function StatRow({
26
+ label,
27
+ value,
28
+ title,
13
29
  }: {
14
- rows: CacheRow[];
30
+ label: string;
31
+ value: string;
32
+ title?: string;
15
33
  }): React.ReactElement {
16
34
  return (
17
- <div className="cache-per-model">
18
- <table>
19
- <thead>
20
- <tr>
21
- <th>Model</th>
22
- <th>Provider</th>
23
- <th>Hits</th>
24
- <th>Saved</th>
25
- <th>Pct</th>
26
- <th>Comp</th>
27
- </tr>
28
- </thead>
29
- <tbody>
30
- {rows.map((r) => (
31
- <tr key={r.model}>
32
- <td>{r.model}</td>
33
- <td>{r.provider}</td>
34
- <td>{r.hits}</td>
35
- <td>{r.saved}</td>
36
- <td>{r.pct}%</td>
37
- <td>{r.compactions}</td>
38
- </tr>
39
- ))}
40
- </tbody>
41
- </table>
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 &amp; 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 <60%, yellow 6080%, red >80%.
5
- * Sublabel: "{tokens} / {contextWindow} tokens ({percent}%)".
4
+ * Color-coded percent fill bar: green <70%, yellow 7089%, 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 >= 80) return "gauge-red";
21
- if (percent >= 60) return "gauge-yellow";
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(0, Math.min(100, pct));
32
+ const fillWidth = Math.max(pct, 1);
38
33
  const cls = severityClass(pct);
39
- const label =
34
+ const tokStr =
40
35
  tokens !== null && tokens !== undefined
41
- ? `${formatTokens(tokens)} / ${formatTokens(contextWindow)} tokens (${pct.toFixed(1)}%)`
42
- : `${formatTokens(contextWindow)} window (usage unknown)`;
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</h3>
42
+ <h3>Context Window — {pct}%</h3>
47
43
  <div
48
44
  className={`gauge-bar ${cls}`}
49
45
  role="meter"