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,49 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dashboard-client/src/components/DataSafetyCard.tsx — Data Safety / Shield.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* 4 fields: Regions Retained, Compressed-Original bytes, Dedup Duplicates,
|
|
5
|
+
* Permanently Deleted (always 0B, shown in green).
|
|
6
|
+
* Plus the safe-note text (verbatim from html.ts).
|
|
6
7
|
*/
|
|
8
|
+
|
|
7
9
|
import type React from "react";
|
|
10
|
+
import { fmtBytes } from "../utils/format";
|
|
8
11
|
|
|
9
12
|
export interface DataSafetyCardProps {
|
|
10
13
|
regionsRetained: number;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
/** Original byte size of compressed data (bytes). */
|
|
15
|
+
compressedOriginalBytes: number;
|
|
16
|
+
/** Number of duplicate chunks collapsed. */
|
|
17
|
+
duplicatesCollapsed: number;
|
|
18
|
+
/** Bytes permanently deleted (always 0). */
|
|
19
|
+
bytesPermanentlyDeleted: number;
|
|
14
20
|
}
|
|
15
21
|
|
|
16
|
-
export function DataSafetyCard(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
storageDedupPct,
|
|
20
|
-
permanentlyDeleted,
|
|
21
|
-
}: DataSafetyCardProps): React.ReactElement {
|
|
22
|
+
export function DataSafetyCard(
|
|
23
|
+
props: DataSafetyCardProps,
|
|
24
|
+
): React.ReactElement {
|
|
22
25
|
return (
|
|
23
|
-
<div className="data-safety-card">
|
|
24
|
-
<
|
|
25
|
-
<div className="
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
</
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
</
|
|
26
|
+
<div className="card data-safety-card">
|
|
27
|
+
<h3>🛡 Data Safety</h3>
|
|
28
|
+
<div className="ov-stat-row">
|
|
29
|
+
<span className="ov-stat-label">Regions Retained</span>
|
|
30
|
+
<span className="ov-stat-value">
|
|
31
|
+
{props.regionsRetained.toLocaleString()}
|
|
32
|
+
</span>
|
|
33
|
+
</div>
|
|
34
|
+
<div className="ov-stat-row">
|
|
35
|
+
<span className="ov-stat-label">Compressed-Original</span>
|
|
36
|
+
<span className="ov-stat-value">
|
|
37
|
+
{fmtBytes(props.compressedOriginalBytes)}
|
|
38
|
+
</span>
|
|
39
|
+
</div>
|
|
40
|
+
<div className="ov-stat-row">
|
|
41
|
+
<span className="ov-stat-label">Dedup Duplicates</span>
|
|
42
|
+
<span className="ov-stat-value">
|
|
43
|
+
{props.duplicatesCollapsed.toLocaleString()}
|
|
44
|
+
</span>
|
|
42
45
|
</div>
|
|
43
|
-
<div className="
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
<div className="ov-stat-row">
|
|
47
|
+
<span className="ov-stat-label">Permanently Deleted</span>
|
|
48
|
+
<span className="ov-stat-value ov-value-ok">
|
|
49
|
+
{fmtBytes(props.bytesPermanentlyDeleted)}
|
|
50
|
+
</span>
|
|
46
51
|
</div>
|
|
52
|
+
<p className="safe-note">
|
|
53
|
+
Every compacted region is kept verbatim (compressed). "Drop" =
|
|
54
|
+
removed from the live window only. We never delete your data.
|
|
55
|
+
</p>
|
|
47
56
|
</div>
|
|
48
57
|
);
|
|
49
58
|
}
|
|
@@ -1,16 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/components/EventCategoryFilter.tsx
|
|
3
|
+
*
|
|
4
|
+
* Category-level filter chips for the Events tab. Filters SSE events by
|
|
5
|
+
* category: all / compact / recall / config / crew / game.
|
|
6
|
+
*/
|
|
7
|
+
|
|
1
8
|
import type React from "react";
|
|
9
|
+
|
|
10
|
+
export type EventCategory =
|
|
11
|
+
| "all"
|
|
12
|
+
| "compact"
|
|
13
|
+
| "recall"
|
|
14
|
+
| "config"
|
|
15
|
+
| "crew"
|
|
16
|
+
| "game";
|
|
17
|
+
|
|
18
|
+
const CATEGORIES: readonly EventCategory[] = [
|
|
19
|
+
"all",
|
|
20
|
+
"compact",
|
|
21
|
+
"recall",
|
|
22
|
+
"config",
|
|
23
|
+
"crew",
|
|
24
|
+
"game",
|
|
25
|
+
] as const;
|
|
26
|
+
|
|
27
|
+
export interface EventCategoryFilterProps {
|
|
28
|
+
/** Currently active category. */
|
|
29
|
+
active: EventCategory;
|
|
30
|
+
/** Called when the user selects a category. */
|
|
31
|
+
onFilter: (category: EventCategory) => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
2
34
|
export function EventCategoryFilter({
|
|
35
|
+
active,
|
|
3
36
|
onFilter,
|
|
4
|
-
}: {
|
|
5
|
-
onFilter: (f: string) => void;
|
|
6
|
-
}): React.ReactElement {
|
|
37
|
+
}: EventCategoryFilterProps): React.ReactElement {
|
|
7
38
|
return (
|
|
8
|
-
<div className="event-filter">
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
39
|
+
<div className="event-category-filter" role="toolbar" aria-label="Event category filter">
|
|
40
|
+
{CATEGORIES.map((cat) => (
|
|
41
|
+
<button
|
|
42
|
+
key={cat}
|
|
43
|
+
type="button"
|
|
44
|
+
className={`category-chip${active === cat ? " active" : ""}`}
|
|
45
|
+
onClick={() => onFilter(cat)}
|
|
46
|
+
aria-pressed={active === cat}
|
|
47
|
+
>
|
|
48
|
+
{cat}
|
|
49
|
+
</button>
|
|
50
|
+
))}
|
|
14
51
|
</div>
|
|
15
52
|
);
|
|
16
53
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/components/LegendCard.tsx — "What these numbers mean" legend.
|
|
3
|
+
*
|
|
4
|
+
* Collapsible <details> with the legend list (verbatim from html.ts).
|
|
5
|
+
* Full-width card (grid-column: 1 / -1).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type React from "react";
|
|
9
|
+
|
|
10
|
+
const LEGEND_ITEMS: ReadonlyArray<React.ReactNode> = [
|
|
11
|
+
<li key="1">
|
|
12
|
+
<b>Original (dropped)</b> — everything compacted away (including
|
|
13
|
+
duplicates caught by dedup). The "in."
|
|
14
|
+
</li>,
|
|
15
|
+
<li key="2">
|
|
16
|
+
<b>Kept (summaries)</b> — compact summaries still held as "memory"
|
|
17
|
+
(the "out").
|
|
18
|
+
</li>,
|
|
19
|
+
<li key="3">
|
|
20
|
+
<b>Freed</b> = dropped − kept — tokens saved so far (higher = better).
|
|
21
|
+
</li>,
|
|
22
|
+
<li key="4">
|
|
23
|
+
<b>Compression %</b> — Freed ÷ Dropped — the headline efficiency
|
|
24
|
+
number. Higher = more space reclaimed.
|
|
25
|
+
</li>,
|
|
26
|
+
<li key="5">
|
|
27
|
+
<b>Storage dedup %</b> — how often new content matched something
|
|
28
|
+
already saved, so no duplicate copy was written.
|
|
29
|
+
</li>,
|
|
30
|
+
<li key="6">
|
|
31
|
+
<b>Data safety</b> — every compacted region is kept verbatim
|
|
32
|
+
(compressed). Nothing is permanently deleted; you can restore any of
|
|
33
|
+
it.
|
|
34
|
+
</li>,
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
export function LegendCard(): React.ReactElement {
|
|
38
|
+
return (
|
|
39
|
+
<div className="card legend-card">
|
|
40
|
+
<details>
|
|
41
|
+
<summary>What these numbers mean</summary>
|
|
42
|
+
<ol className="legend-list">{LEGEND_ITEMS}</ol>
|
|
43
|
+
<p className="legend-note">
|
|
44
|
+
Hover any label above for a quick explanation.
|
|
45
|
+
</p>
|
|
46
|
+
</details>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dashboard-client/src/components/ModelBadge.tsx — model + provider + rates.
|
|
3
3
|
*
|
|
4
|
-
* Shows the active model name,
|
|
5
|
-
*
|
|
4
|
+
* Shows the active model name, provider, and input/output rates.
|
|
5
|
+
* Tooltips copied verbatim from html.ts. Used in the Metrics tab header.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type React from "react";
|
|
@@ -14,12 +14,29 @@ export interface ModelBadgeProps {
|
|
|
14
14
|
providerName: string;
|
|
15
15
|
/** Machine-readable provider identifier. */
|
|
16
16
|
provider: string;
|
|
17
|
-
/**
|
|
17
|
+
/** USD per input token (from model pricing). */
|
|
18
18
|
inputRate: number;
|
|
19
|
-
/**
|
|
19
|
+
/** USD per output token (from model pricing). */
|
|
20
20
|
outputRate: number;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
function Field({
|
|
24
|
+
label,
|
|
25
|
+
value,
|
|
26
|
+
title,
|
|
27
|
+
}: {
|
|
28
|
+
label: string;
|
|
29
|
+
value: string;
|
|
30
|
+
title?: string;
|
|
31
|
+
}): React.ReactElement {
|
|
32
|
+
return (
|
|
33
|
+
<div className="model-field" title={title}>
|
|
34
|
+
<span className="model-field-label">{label}</span>
|
|
35
|
+
<span className="model-field-value">{value}</span>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
23
40
|
export function ModelBadge({
|
|
24
41
|
name,
|
|
25
42
|
providerName,
|
|
@@ -27,15 +44,29 @@ export function ModelBadge({
|
|
|
27
44
|
inputRate,
|
|
28
45
|
outputRate,
|
|
29
46
|
}: ModelBadgeProps): React.ReactElement {
|
|
47
|
+
const providerDisplay = providerName || provider || "—";
|
|
30
48
|
return (
|
|
31
49
|
<div className="model-badge">
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
50
|
+
<Field
|
|
51
|
+
label="Model"
|
|
52
|
+
value={name}
|
|
53
|
+
title="The model pi is currently using — its pricing drives the cost figure."
|
|
54
|
+
/>
|
|
55
|
+
<Field
|
|
56
|
+
label="Provider"
|
|
57
|
+
value={providerDisplay}
|
|
58
|
+
title="The provider serving the model."
|
|
59
|
+
/>
|
|
60
|
+
<Field
|
|
61
|
+
label="Input Rate"
|
|
62
|
+
value={`$${inputRate.toFixed(6)}`}
|
|
63
|
+
title="USD per input token, from the model's pricing."
|
|
64
|
+
/>
|
|
65
|
+
<Field
|
|
66
|
+
label="Output Rate"
|
|
67
|
+
value={`$${outputRate.toFixed(6)}`}
|
|
68
|
+
title="USD per output token, from the model's pricing."
|
|
69
|
+
/>
|
|
39
70
|
</div>
|
|
40
71
|
);
|
|
41
72
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/components/PerfCards.tsx — 5 perf metric cards.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the old html.ts perf grid: model latency, throughput, process,
|
|
5
|
+
* snapshot cost, and TUI lag proxy. Uses the existing perf-metric CSS
|
|
6
|
+
* classes from repos-metrics.css plus metrics-extra.css for card headers.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type React from "react";
|
|
10
|
+
import type { PerfResponse } from "@contracts";
|
|
11
|
+
|
|
12
|
+
/** Format milliseconds (em-dash for null/undefined). */
|
|
13
|
+
function fmtMs(v: number | null | undefined): string {
|
|
14
|
+
return v == null ? "\u2014" : v >= 100 ? `${Math.round(v)}ms` : `${v.toFixed(1)}ms`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Format a number with fixed decimals (em-dash for null/non-number). */
|
|
18
|
+
function fmtNum(v: number | null | undefined, dec: number): string {
|
|
19
|
+
return v == null || typeof v !== "number" ? "\u2014" : v.toFixed(dec);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Format a diag counter (em-dash for null). */
|
|
23
|
+
function fmtDiag(v: number | null | undefined): string {
|
|
24
|
+
return v == null ? "\u2014" : String(v);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface PerfCardsProps {
|
|
28
|
+
perf: PerfResponse;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A single stat row inside a perf card. */
|
|
32
|
+
function Stat({ label, value }: { label: string; value: string }): React.ReactElement {
|
|
33
|
+
return (
|
|
34
|
+
<div className="perf-metric">
|
|
35
|
+
<span className="perf-label">{label}</span>
|
|
36
|
+
<span className="perf-value">{value}</span>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Card wrapper with a title. */
|
|
42
|
+
function Card({ title, children }: { title: string; children: React.ReactNode }): React.ReactElement {
|
|
43
|
+
return (
|
|
44
|
+
<div className="perf-card">
|
|
45
|
+
<h3 className="perf-card-title">{title}</h3>
|
|
46
|
+
<div className="perf-card-body">{children}</div>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function PerfCards({ perf }: PerfCardsProps): React.ReactElement {
|
|
52
|
+
const cpuTxt = `${fmtNum(perf.cpu_user_ms.latest, 1)} / ${fmtNum(perf.cpu_sys_ms.latest, 1)} ms`;
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div className="perf-cards-grid">
|
|
56
|
+
<Card title="Model latency">
|
|
57
|
+
<Stat label="Turn p50" value={fmtMs(perf.turn_latency_ms.p50)} />
|
|
58
|
+
<Stat label="Turn p95" value={fmtMs(perf.turn_latency_ms.p95)} />
|
|
59
|
+
<Stat label="Provider p50" value={fmtMs(perf.provider_latency_ms.p50)} />
|
|
60
|
+
<Stat label="Provider p95" value={fmtMs(perf.provider_latency_ms.p95)} />
|
|
61
|
+
</Card>
|
|
62
|
+
|
|
63
|
+
<Card title="Throughput">
|
|
64
|
+
<Stat label="TPS (avg)" value={fmtNum(perf.tps.avg, 1)} />
|
|
65
|
+
<Stat
|
|
66
|
+
label="Cache hit %"
|
|
67
|
+
value={`${fmtNum(perf.cache_hit_pct.avg, 1)}%`}
|
|
68
|
+
/>
|
|
69
|
+
</Card>
|
|
70
|
+
|
|
71
|
+
<Card title="Process">
|
|
72
|
+
<Stat label="RSS" value={`${fmtNum(perf.rss_mb.latest, 1)} MB`} />
|
|
73
|
+
<Stat label="Heap" value={`${fmtNum(perf.heap_mb.latest, 1)} MB`} />
|
|
74
|
+
<Stat label="CPU user / sys" value={cpuTxt} />
|
|
75
|
+
</Card>
|
|
76
|
+
|
|
77
|
+
<Card title="Snapshot cost">
|
|
78
|
+
<Stat label="DB recompute p50" value={fmtMs(perf.db_recompute_ms.p50)} />
|
|
79
|
+
<Stat label="DB recompute p95" value={fmtMs(perf.db_recompute_ms.p95)} />
|
|
80
|
+
<Stat label="Disk write p50" value={fmtMs(perf.disk_write_ms.p50)} />
|
|
81
|
+
</Card>
|
|
82
|
+
|
|
83
|
+
<Card title="TUI lag proxy">
|
|
84
|
+
<Stat label="Live-trim fires" value={fmtDiag(perf.diag?.liveTrimFires)} />
|
|
85
|
+
<Stat label="Cache replays" value={fmtDiag(perf.diag?.liveTrimReplays)} />
|
|
86
|
+
<Stat label="Fast-gate skips" value={fmtDiag(perf.diag?.ctxFastGate)} />
|
|
87
|
+
<span className="perf-card-note">skip vs recompute vs replay cadence</span>
|
|
88
|
+
</Card>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/components/RepoAllSessionsCard.tsx — Repo (all sessions) card.
|
|
3
|
+
*
|
|
4
|
+
* 7 fields: Checkpoints, Original, Kept, Freed, Sessions, Collapsed, Storage Dedup.
|
|
5
|
+
* Plus a compression meter bar (green ≥90%, yellow ≥60%, red <60%).
|
|
6
|
+
*
|
|
7
|
+
* NOTE: storageDedupRate, compressionPct, dedupPct are 0–1 fractions
|
|
8
|
+
* at runtime (despite contract saying 0–100).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type React from "react";
|
|
12
|
+
import { fmtPctFromFraction } from "../utils/format";
|
|
13
|
+
|
|
14
|
+
export interface RepoAllSessionsCardProps {
|
|
15
|
+
checkpointCount: number;
|
|
16
|
+
/** Original tokens dropped (repo-level). */
|
|
17
|
+
tokensIn: number;
|
|
18
|
+
/** Kept summary tokens (repo-level). */
|
|
19
|
+
tokensOut: number;
|
|
20
|
+
/** Tokens freed (repo-level). */
|
|
21
|
+
tokensFreed: number;
|
|
22
|
+
/** Number of sessions. */
|
|
23
|
+
sessionCount: number;
|
|
24
|
+
/** Duplicate chunks collapsed (repo-level). */
|
|
25
|
+
dedupCollapsed: number;
|
|
26
|
+
/** Storage dedup rate — 0–1 fraction. */
|
|
27
|
+
storageDedupRate: number;
|
|
28
|
+
/** Repo compression ratio — 0–1 fraction. */
|
|
29
|
+
compressionPct: number;
|
|
30
|
+
/** Repo dedup contribution — 0–1 fraction. */
|
|
31
|
+
dedupPct: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function compressClass(sp: number): string {
|
|
35
|
+
if (sp >= 0.9) return "meter-green";
|
|
36
|
+
if (sp >= 0.6) return "meter-yellow";
|
|
37
|
+
return "meter-red";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function StatRow({
|
|
41
|
+
label,
|
|
42
|
+
value,
|
|
43
|
+
}: {
|
|
44
|
+
label: string;
|
|
45
|
+
value: string | number;
|
|
46
|
+
}): React.ReactElement {
|
|
47
|
+
return (
|
|
48
|
+
<div className="ov-stat-row">
|
|
49
|
+
<span className="ov-stat-label">{label}</span>
|
|
50
|
+
<span className="ov-stat-value">{value}</span>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function RepoAllSessionsCard(
|
|
56
|
+
props: RepoAllSessionsCardProps,
|
|
57
|
+
): React.ReactElement {
|
|
58
|
+
const sp = props.compressionPct;
|
|
59
|
+
const barCls = compressClass(sp);
|
|
60
|
+
const barWidth = Math.max(sp * 100, 0.5);
|
|
61
|
+
const compressSub = `${fmtPctFromFraction(sp)} tokens saved · dedup: ${fmtPctFromFraction(props.dedupPct)}`;
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div className="card repo-sessions-card">
|
|
65
|
+
<h3>Repo (all sessions)</h3>
|
|
66
|
+
<StatRow
|
|
67
|
+
label="Checkpoints"
|
|
68
|
+
value={props.checkpointCount.toLocaleString()}
|
|
69
|
+
/>
|
|
70
|
+
<StatRow
|
|
71
|
+
label="Original"
|
|
72
|
+
value={props.tokensIn.toLocaleString()}
|
|
73
|
+
/>
|
|
74
|
+
<StatRow
|
|
75
|
+
label="Kept"
|
|
76
|
+
value={props.tokensOut.toLocaleString()}
|
|
77
|
+
/>
|
|
78
|
+
<StatRow
|
|
79
|
+
label="Freed"
|
|
80
|
+
value={props.tokensFreed.toLocaleString()}
|
|
81
|
+
/>
|
|
82
|
+
<StatRow
|
|
83
|
+
label="Sessions"
|
|
84
|
+
value={props.sessionCount.toLocaleString()}
|
|
85
|
+
/>
|
|
86
|
+
<StatRow label="Collapsed" value={props.dedupCollapsed} />
|
|
87
|
+
<StatRow
|
|
88
|
+
label="Storage Dedup"
|
|
89
|
+
value={fmtPctFromFraction(props.storageDedupRate)}
|
|
90
|
+
/>
|
|
91
|
+
<div className="ov-compression-meter">
|
|
92
|
+
<div className="meter-track">
|
|
93
|
+
<div
|
|
94
|
+
className={`meter-fill ${barCls}`}
|
|
95
|
+
style={{ width: `${barWidth}%` }}
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
<span className="meter-sub">{compressSub}</span>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
@@ -20,6 +20,13 @@ function fmt(n: number): string {
|
|
|
20
20
|
return String(n);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/** Format bytes → MiB/KiB/B (matches html.ts fmtBytesTop). */
|
|
24
|
+
function fmtBytesTop(b: number): string {
|
|
25
|
+
if (b >= 1_048_576) return `${(b / 1_048_576).toFixed(1)} MiB`;
|
|
26
|
+
if (b >= 1024) return `${(b / 1024).toFixed(1)} KiB`;
|
|
27
|
+
return `${b} B`;
|
|
28
|
+
}
|
|
29
|
+
|
|
23
30
|
function formatTs(ts: number | null): string {
|
|
24
31
|
if (ts === null || ts === undefined) return "never";
|
|
25
32
|
try {
|
|
@@ -117,8 +124,8 @@ export function RepoDetailModal({
|
|
|
117
124
|
<dd>{formatTs(repo.lastCompactedAt)}</dd>
|
|
118
125
|
<dt>Last seen</dt>
|
|
119
126
|
<dd>{formatTs(repo.lastSeen)}</dd>
|
|
120
|
-
<dt>Compressed
|
|
121
|
-
<dd>{
|
|
127
|
+
<dt>Compressed-Original</dt>
|
|
128
|
+
<dd>{fmtBytesTop(repo.compressedOriginalBytes)}</dd>
|
|
122
129
|
</dl>
|
|
123
130
|
</div>
|
|
124
131
|
</div>
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dashboard-client/src/components/RepoTable.tsx — sortable, searchable repo table.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 6 columns: Repo, Model, Checkpoints, Tokens Saved, Retained, Last Compacted.
|
|
5
5
|
* Client-side sort (click header to toggle asc/desc). Search by displayName
|
|
6
|
-
* (case-insensitive substring). Default sort:
|
|
6
|
+
* (case-insensitive substring). Default sort: Last Compacted DESC.
|
|
7
|
+
* Row click opens RepoDetailModal.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
import type React from "react";
|
|
@@ -21,30 +22,31 @@ type SortKey =
|
|
|
21
22
|
| "modelName"
|
|
22
23
|
| "checkpointCount"
|
|
23
24
|
| "tokensSaved"
|
|
24
|
-
| "
|
|
25
|
-
| "
|
|
25
|
+
| "compressedOriginalBytes"
|
|
26
|
+
| "lastCompactedAt";
|
|
26
27
|
|
|
27
28
|
type SortDir = "asc" | "desc";
|
|
28
29
|
|
|
29
30
|
const COLUMNS: Array<{ key: SortKey; label: string }> = [
|
|
30
|
-
{ key: "displayName", label: "
|
|
31
|
+
{ key: "displayName", label: "Repo" },
|
|
31
32
|
{ key: "modelName", label: "Model" },
|
|
32
33
|
{ key: "checkpointCount", label: "Checkpoints" },
|
|
33
|
-
{ key: "tokensSaved", label: "Saved" },
|
|
34
|
-
{ key: "
|
|
35
|
-
{ key: "
|
|
34
|
+
{ key: "tokensSaved", label: "Tokens Saved" },
|
|
35
|
+
{ key: "compressedOriginalBytes", label: "Retained" },
|
|
36
|
+
{ key: "lastCompactedAt", label: "Last Compacted" },
|
|
36
37
|
];
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (
|
|
41
|
-
return
|
|
39
|
+
/** Format bytes → MiB/KiB/B (matches html.ts fmtBytesTop). */
|
|
40
|
+
function fmtBytesTop(b: number): string {
|
|
41
|
+
if (b >= 1_048_576) return `${(b / 1_048_576).toFixed(1)} MiB`;
|
|
42
|
+
if (b >= 1024) return `${(b / 1024).toFixed(1)} KiB`;
|
|
43
|
+
return `${b} B`;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
function formatTs(ts: number | null): string {
|
|
45
|
-
if (ts === null || ts === undefined) return "
|
|
47
|
+
if (ts === null || ts === undefined) return "\u2014";
|
|
46
48
|
try {
|
|
47
|
-
return new Date(ts).
|
|
49
|
+
return new Date(ts).toLocaleString();
|
|
48
50
|
} catch {
|
|
49
51
|
return String(ts);
|
|
50
52
|
}
|
|
@@ -87,7 +89,11 @@ export function RepoTable({
|
|
|
87
89
|
} else {
|
|
88
90
|
setSortKey(key);
|
|
89
91
|
setSortDir(
|
|
90
|
-
key === "lastCompactedAt" ||
|
|
92
|
+
key === "lastCompactedAt" ||
|
|
93
|
+
key === "tokensSaved" ||
|
|
94
|
+
key === "compressedOriginalBytes"
|
|
95
|
+
? "desc"
|
|
96
|
+
: "asc",
|
|
91
97
|
);
|
|
92
98
|
}
|
|
93
99
|
};
|
|
@@ -128,19 +134,21 @@ export function RepoTable({
|
|
|
128
134
|
<tr>
|
|
129
135
|
<td colSpan={COLUMNS.length} className="repo-empty">
|
|
130
136
|
{repos.length === 0
|
|
131
|
-
? "No
|
|
137
|
+
? "No repositories registered yet."
|
|
132
138
|
: "No repos match your search."}
|
|
133
139
|
</td>
|
|
134
140
|
</tr>
|
|
135
141
|
)}
|
|
136
142
|
{filtered.map((repo) => (
|
|
137
143
|
<tr key={repo.stateDir} onClick={() => onSelect(repo)}>
|
|
138
|
-
<td className="repo-name"
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
<td>{
|
|
142
|
-
<td>{
|
|
143
|
-
<td>{repo.
|
|
144
|
+
<td className="repo-name" title={repo.repoRoot}>
|
|
145
|
+
{repo.displayName}
|
|
146
|
+
</td>
|
|
147
|
+
<td className="repo-model">{repo.modelName ?? "\u2014"}</td>
|
|
148
|
+
<td className="num">{repo.checkpointCount.toLocaleString()}</td>
|
|
149
|
+
<td className="num">{repo.tokensSaved.toLocaleString()}</td>
|
|
150
|
+
<td className="num">{fmtBytesTop(repo.compressedOriginalBytes)}</td>
|
|
151
|
+
<td className="num">{formatTs(repo.lastCompactedAt)}</td>
|
|
144
152
|
</tr>
|
|
145
153
|
))}
|
|
146
154
|
</tbody>
|