pi-mega-compact 0.11.5 → 0.11.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/extensions/dashboard-server/api-contracts/cache-stripes.js +8 -0
- package/dist/extensions/dashboard-server/api-contracts/endpoints.js +13 -0
- package/dist/extensions/dashboard-server/api-contracts/memory-map.js +1 -0
- package/dist/extensions/dashboard-server/routes-cache.js +28 -0
- package/dist/extensions/dashboard-server/routes-game.js +3 -0
- package/dist/extensions/dashboard-server/routes-memory-map.js +50 -0
- package/dist/extensions/dashboard-server/routes-setup.js +105 -0
- package/dist/extensions/dashboard-server/routes.js +3 -1
- package/dist/extensions/dashboard-server/server.js +7 -1
- package/dist/extensions/mega-compact.js +5 -0
- package/dist/extensions/mega-config.js +5 -0
- package/dist/extensions/mega-events/context-handler.js +10 -2
- package/dist/extensions/mega-events/perf-handler.js +83 -1
- package/dist/extensions/mega-events/separated-prompt.js +248 -0
- package/dist/extensions/mega-runtime/env-loader.js +56 -0
- package/dist/extensions/mega-runtime/reset-runtime.js +1 -0
- package/dist/extensions/mega-runtime/runtime-snapshot.js +1 -0
- package/dist/extensions/mega-runtime/runtime.js +1 -0
- package/dist/extensions/mega-runtime/snapshot.js +1 -0
- package/dist/extensions/mega-runtime/widget.js +5 -0
- package/dist/src/cache-stripe-impl.js +288 -0
- package/dist/src/cache-stripe.js +11 -0
- package/dist/src/config/stopwords.js +24 -0
- package/dist/src/config.js +17 -0
- package/dist/src/memoryGraph.js +299 -0
- package/dist/src/monitoring.js +13 -0
- package/dist/src/queryExpansion.js +163 -0
- package/dist/src/queryReformulation/cache.js +123 -0
- package/dist/src/queryReformulation/rrf.js +29 -0
- package/dist/src/queryReformulation/tfidf.js +61 -0
- package/dist/src/queryReformulation/vagueness.js +33 -0
- package/dist/src/queryReformulation.js +225 -0
- package/dist/src/recallMetrics.js +258 -0
- package/dist/src/store/sqlite/cache-stripes.js +133 -0
- package/dist/src/store/sqlite/fts5-search.js +68 -0
- package/dist/src/store/sqlite/perf-samples.js +1 -0
- package/dist/src/store/sqlite/schema.js +60 -1
- package/dist/src/store/sqlite.js +1 -0
- package/dist/src/store/vectorIndex.js +29 -5
- package/dist/src/tiered-router/types.js +18 -0
- package/dist/src/tieredRouter.js +421 -0
- package/dist/src/vectorStore.js +8 -6
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-BoL7qBOJ.js → AchievementTiles-DR9cLGDL.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-BoL7qBOJ.js.map → AchievementTiles-DR9cLGDL.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-BFDE3HYc.js → AchievementsTab-svNUgJ1e.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-BFDE3HYc.js.map → AchievementsTab-svNUgJ1e.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/CacheTab-ASwhOMjR.js +2 -0
- package/extensions/dashboard-client/dist/assets/CacheTab-ASwhOMjR.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{ConfigTab-DDFE-ckA.js → ConfigTab-CmmWheuW.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ConfigTab-DDFE-ckA.js.map → ConfigTab-CmmWheuW.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-B6UxZ7PM.js → EventsTab-CFE7jLsa.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-B6UxZ7PM.js.map → EventsTab-CFE7jLsa.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/GameTab-CJAX_th_.js +2 -0
- package/extensions/dashboard-client/dist/assets/{GameTab-DehUX-KE.js.map → GameTab-CJAX_th_.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-0GWRt4wn.js → MaintenanceTab-P4SUsvMf.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-0GWRt4wn.js.map → MaintenanceTab-P4SUsvMf.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js +2 -0
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{MetricsTab-C3rwMVsN.js → MetricsTab-CHQscixy.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-C3rwMVsN.js.map → MetricsTab-CHQscixy.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-39O9_x5j.js → OverviewTab-DBs4DJdd.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-39O9_x5j.js.map → OverviewTab-DBs4DJdd.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-Cfj6Q5sH.js → ReposTab-DeFz76L5.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-Cfj6Q5sH.js.map → ReposTab-DeFz76L5.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-DXDTCi3r.js → SessionsTab-BWNsj1OB.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-DXDTCi3r.js.map → SessionsTab-BWNsj1OB.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/SetupTab-uS6A0xmw.js +2 -0
- package/extensions/dashboard-client/dist/assets/SetupTab-uS6A0xmw.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-Cw1dJ4e9.js → TimeSavedCard-CiqCOQuL.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-Cw1dJ4e9.js.map → TimeSavedCard-CiqCOQuL.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TopicsTab-BAw972BV.js → TopicsTab-BWWQxltv.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TopicsTab-BAw972BV.js.map → TopicsTab-BWWQxltv.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-Cf5mGIdl.js → TurnsTab-CVz4f4-o.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-Cf5mGIdl.js.map → TurnsTab-CVz4f4-o.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/index-CUNn22iV.css +1 -0
- package/extensions/dashboard-client/dist/assets/{index-4gQ7auad.js → index-DrktNOoT.js} +11 -11
- package/extensions/dashboard-client/dist/assets/index-DrktNOoT.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{useSSE-DNgMuyP6.js → useSSE-Bf5LOLSi.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-DNgMuyP6.js.map → useSSE-Bf5LOLSi.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +2 -2
- package/extensions/dashboard-client/src/App.tsx +24 -8
- package/extensions/dashboard-client/src/api/client.ts +15 -0
- package/extensions/dashboard-client/src/components/CacheHitRateTrendCard.tsx +100 -0
- package/extensions/dashboard-client/src/components/StripeDistributionCard.tsx +110 -0
- package/extensions/dashboard-client/src/components/TabBar.tsx +49 -4
- package/extensions/dashboard-client/src/main.tsx +1 -0
- package/extensions/dashboard-client/src/styles/base.css +30 -0
- package/extensions/dashboard-client/src/styles/cache.css +167 -0
- package/extensions/dashboard-client/src/tabs/CacheTab.tsx +75 -13
- package/extensions/dashboard-client/src/tabs/MemoryMapTab.tsx +440 -0
- package/extensions/dashboard-client/src/tabs/SetupTab.tsx +65 -2
- package/extensions/dashboard-server/api-contracts/cache-stripes.ts +49 -0
- package/extensions/dashboard-server/api-contracts/endpoints.ts +31 -1
- package/extensions/dashboard-server/api-contracts/index.ts +16 -1
- package/extensions/dashboard-server/api-contracts/memory-map.ts +50 -0
- package/extensions/dashboard-server/api-contracts/setup.ts +22 -0
- package/extensions/dashboard-server/routes-cache.ts +36 -1
- package/extensions/dashboard-server/routes-game.ts +3 -0
- package/extensions/dashboard-server/routes-memory-map.ts +105 -0
- package/extensions/dashboard-server/routes-setup.ts +112 -0
- package/extensions/dashboard-server/routes.ts +3 -1
- package/extensions/dashboard-server/server.ts +6 -0
- package/extensions/mega-compact.ts +5 -0
- package/extensions/mega-config.ts +17 -0
- package/extensions/mega-events/context-handler.ts +10 -2
- package/extensions/mega-events/perf-handler.ts +111 -1
- package/extensions/mega-events/separated-prompt.ts +329 -0
- package/extensions/mega-runtime/env-loader.ts +55 -0
- package/extensions/mega-runtime/helpers.ts +2 -0
- package/extensions/mega-runtime/reset-runtime.ts +1 -0
- package/extensions/mega-runtime/runtime-snapshot.ts +1 -0
- package/extensions/mega-runtime/runtime.ts +1 -0
- package/extensions/mega-runtime/snapshot.ts +6 -0
- package/extensions/mega-runtime/widget-types.ts +3 -0
- package/extensions/mega-runtime/widget.ts +5 -0
- package/package.json +1 -1
- package/src/cache-stripe-impl.ts +385 -0
- package/src/cache-stripe.ts +18 -0
- package/src/config/stopwords.ts +25 -0
- package/src/config.ts +26 -0
- package/src/memoryGraph.ts +438 -0
- package/src/monitoring.ts +35 -0
- package/src/queryExpansion.ts +206 -0
- package/src/queryReformulation/cache.ts +169 -0
- package/src/queryReformulation/rrf.ts +51 -0
- package/src/queryReformulation/tfidf.ts +79 -0
- package/src/queryReformulation/vagueness.ts +38 -0
- package/src/queryReformulation.ts +355 -0
- package/src/recallMetrics.ts +388 -0
- package/src/store/sqlite/cache-stripes.ts +196 -0
- package/src/store/sqlite/fts5-search.ts +90 -0
- package/src/store/sqlite/perf-samples.ts +3 -1
- package/src/store/sqlite/schema.ts +60 -1
- package/src/store/sqlite.ts +1 -0
- package/src/store/vectorIndex.ts +38 -7
- package/src/tiered-router/types.ts +64 -0
- package/src/tieredRouter.ts +489 -0
- package/src/vectorStore.ts +9 -5
- package/extensions/dashboard-client/dist/assets/CacheTab-eKVc-NV1.js +0 -2
- package/extensions/dashboard-client/dist/assets/CacheTab-eKVc-NV1.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/GameTab-DehUX-KE.js +0 -2
- package/extensions/dashboard-client/dist/assets/SetupTab-mMlfunJa.js +0 -2
- package/extensions/dashboard-client/dist/assets/SetupTab-mMlfunJa.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/index-4gQ7auad.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/index-BEdKlpAB.css +0 -1
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tieredRouter.ts — S44 three-tier latency-aware recall routing (impl).
|
|
3
|
+
*
|
|
4
|
+
* Three tiers:
|
|
5
|
+
* L0: in-memory LRU cache (recent queries -> results).
|
|
6
|
+
* L1: FTS5 trigram BM25 search (fast, approximate).
|
|
7
|
+
* L2: PGlite HNSW vector search via vectorStore.searchAsync (accurate, slower).
|
|
8
|
+
*
|
|
9
|
+
* Tiered fallback: try L0, miss -> L1, insufficient -> L2, L2 throw -> sync call.
|
|
10
|
+
* Latency-budget configurable per tier. Real `Date.now()` delta instrumentation.
|
|
11
|
+
*
|
|
12
|
+
* Pi-agnostic: no pi runtime types. Uses `openStore` for L1 and optionally
|
|
13
|
+
* the existing VectorStore / vectorSearchAsync for L2.
|
|
14
|
+
*/
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
17
|
+
import { dirname, join } from "node:path";
|
|
18
|
+
import { openStore } from "./store/sqlite/utils.js";
|
|
19
|
+
import { fts5SearchScoped } from "./store/sqlite/fts5-search.js";
|
|
20
|
+
import { listCheckpoints } from "./store/sqlite.js";
|
|
21
|
+
import { getStateDir } from "./store.js";
|
|
22
|
+
import { vectorSearchAsync } from "./vector-search.js";
|
|
23
|
+
import { DEFAULT_CACHE_SIZE, DEFAULT_BUDGET_L0_MS, DEFAULT_BUDGET_L1_MS, DEFAULT_BUDGET_L2_MS, DEFAULT_LOG_CADENCE, } from "./tiered-router/types.js";
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Env-based config helpers
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
function envBool(name, def) {
|
|
28
|
+
const v = process.env[name];
|
|
29
|
+
if (v === undefined)
|
|
30
|
+
return def;
|
|
31
|
+
return v === "true" || v === "1";
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Load fts5MaxBm25 from calibration.json or env override.
|
|
35
|
+
*/
|
|
36
|
+
function loadFts5MaxBm25(stateDir) {
|
|
37
|
+
// Env override wins.
|
|
38
|
+
const envVal = process.env.MEGACOMPACT_FTS5_MAX_BM25;
|
|
39
|
+
if (envVal !== undefined) {
|
|
40
|
+
const n = Number(envVal);
|
|
41
|
+
if (Number.isFinite(n) && n >= 0) {
|
|
42
|
+
return { fts5MaxBm25: n, uncalibrated: false };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Try calibration.json.
|
|
46
|
+
const calPath = join(stateDir, "calibration.json");
|
|
47
|
+
if (existsSync(calPath)) {
|
|
48
|
+
try {
|
|
49
|
+
const raw = readFileSync(calPath, "utf-8");
|
|
50
|
+
// PREVENT-001: null check on JSON.parse
|
|
51
|
+
const parsed = JSON.parse(raw);
|
|
52
|
+
const cal = parsed;
|
|
53
|
+
if (typeof cal.fts5MaxBm25 === "number" && cal.fts5MaxBm25 >= 0) {
|
|
54
|
+
return { fts5MaxBm25: cal.fts5MaxBm25, uncalibrated: false };
|
|
55
|
+
}
|
|
56
|
+
return { fts5MaxBm25: null, uncalibrated: true };
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Malformed calibration file -- treat as uncalibrated.
|
|
60
|
+
return { fts5MaxBm25: null, uncalibrated: true };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { fts5MaxBm25: null, uncalibrated: true };
|
|
64
|
+
}
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Cache helpers
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
/**
|
|
69
|
+
* Deterministic cache key from (sessionId, query, k).
|
|
70
|
+
* Uses first 12 hex chars of SHA-256 (collision probability negligible for a
|
|
71
|
+
* local cache -- full collisions across different queries produce identical keys
|
|
72
|
+
* only if the model IDs and query text collide, which is a cache-containment
|
|
73
|
+
* artifact, not a correctness bug).
|
|
74
|
+
*/
|
|
75
|
+
function cacheKey(sessionId, query, k) {
|
|
76
|
+
const payload = `${sessionId ?? "__cross__"}||${query}||${k}`;
|
|
77
|
+
return createHash("sha256").update(payload).digest("hex").slice(0, 12);
|
|
78
|
+
}
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// TieredRouter
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
export class TieredRouter {
|
|
83
|
+
// ---------- LRU cache ----------
|
|
84
|
+
cacheSize;
|
|
85
|
+
cacheMap = new Map();
|
|
86
|
+
// ---------- Per-tier latency budgets ----------
|
|
87
|
+
budgetL0Ms;
|
|
88
|
+
budgetL1Ms;
|
|
89
|
+
budgetL2Ms;
|
|
90
|
+
// ---------- Calibration ----------
|
|
91
|
+
/** Max BM25 score achievable on this corpus (null = uncalibrated). */
|
|
92
|
+
fts5MaxBm25;
|
|
93
|
+
/** True when no calibration file or env override is available. */
|
|
94
|
+
uncalibrated;
|
|
95
|
+
/**
|
|
96
|
+
* Conservative fallback gate fraction for the L1 confidence check.
|
|
97
|
+
* When uncalibrated, we require L1 score >= fraction * (some heuristic ceiling)
|
|
98
|
+
* before skipping L2. A real calibration sets this dynamically.
|
|
99
|
+
*/
|
|
100
|
+
l1ConfidenceFraction;
|
|
101
|
+
// ---------- State dir + log path ----------
|
|
102
|
+
stateDir;
|
|
103
|
+
logPath;
|
|
104
|
+
// ---------- Metrics ----------
|
|
105
|
+
l0Hits = 0;
|
|
106
|
+
l0Misses = 0;
|
|
107
|
+
l1Hits = 0;
|
|
108
|
+
l1Misses = 0;
|
|
109
|
+
l2Hits = 0;
|
|
110
|
+
l2Misses = 0;
|
|
111
|
+
totalQueries = 0;
|
|
112
|
+
accLatencyL0 = 0;
|
|
113
|
+
accLatencyL1 = 0;
|
|
114
|
+
accLatencyL2 = 0;
|
|
115
|
+
logCadence;
|
|
116
|
+
/** Hashed query -> embedding cache (avoids re-embedding L1 hits for L2). */
|
|
117
|
+
embeddingCache = new Map();
|
|
118
|
+
constructor(opts) {
|
|
119
|
+
const sd = opts?.stateDir ?? getStateDir();
|
|
120
|
+
this.stateDir = sd;
|
|
121
|
+
this.logPath = join(sd, "tiered-router-events.log");
|
|
122
|
+
this.cacheSize = opts?.cacheSize ?? DEFAULT_CACHE_SIZE;
|
|
123
|
+
this.budgetL0Ms = opts?.budgetL0Ms ?? DEFAULT_BUDGET_L0_MS;
|
|
124
|
+
this.budgetL1Ms = opts?.budgetL1Ms ?? DEFAULT_BUDGET_L1_MS;
|
|
125
|
+
this.budgetL2Ms = opts?.budgetL2Ms ?? DEFAULT_BUDGET_L2_MS;
|
|
126
|
+
this.logCadence = opts?.logCadence ?? DEFAULT_LOG_CADENCE;
|
|
127
|
+
// Calibration: explicit override > env/file > uncalibrated.
|
|
128
|
+
if (opts?.fts5MaxBm25 !== undefined) {
|
|
129
|
+
this.fts5MaxBm25 = opts.fts5MaxBm25;
|
|
130
|
+
this.uncalibrated = opts.fts5MaxBm25 === null;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
const cal = loadFts5MaxBm25(this.stateDir);
|
|
134
|
+
this.fts5MaxBm25 = cal.fts5MaxBm25;
|
|
135
|
+
this.uncalibrated = cal.uncalibrated;
|
|
136
|
+
}
|
|
137
|
+
// Conservative fallback: when uncalibrated, use a small fraction
|
|
138
|
+
// (10% of an assumed ceiling 0.5) to bias toward L2 fallback.
|
|
139
|
+
// A real calibration enables the full confidence gate.
|
|
140
|
+
this.l1ConfidenceFraction = this.fts5MaxBm25 != null ? 0.7 : 0.1;
|
|
141
|
+
}
|
|
142
|
+
// -----------------------------------------------------------------------
|
|
143
|
+
// Public API
|
|
144
|
+
// -----------------------------------------------------------------------
|
|
145
|
+
/**
|
|
146
|
+
* Three-tier recall routing: L0 (cache) -> L1 (FTS5) -> L2 (PGlite vec).
|
|
147
|
+
*
|
|
148
|
+
* @param query The search query text.
|
|
149
|
+
* @param opts Options including sessionId, k, flag overrides.
|
|
150
|
+
* @param store The VectorStore instance (needed for L2 hydration).
|
|
151
|
+
* @returns Hits ranked by the tier that satisfied the query.
|
|
152
|
+
*/
|
|
153
|
+
async route(query, opts, store) {
|
|
154
|
+
this.totalQueries++;
|
|
155
|
+
const k = opts.k ?? 10;
|
|
156
|
+
const sessionId = opts.sessionId;
|
|
157
|
+
// ---- L0: in-memory LRU cache ----
|
|
158
|
+
const ck = cacheKey(sessionId, query, k);
|
|
159
|
+
if (!opts.bypassCache) {
|
|
160
|
+
try {
|
|
161
|
+
const t0L0 = Date.now();
|
|
162
|
+
const cached = this.cacheMap.get(ck);
|
|
163
|
+
const latL0 = Date.now() - t0L0;
|
|
164
|
+
this.accLatencyL0 += latL0;
|
|
165
|
+
if (cached) {
|
|
166
|
+
this.l0Hits++;
|
|
167
|
+
// Promote to front (delete + set).
|
|
168
|
+
this.cacheMap.delete(ck);
|
|
169
|
+
this.cacheMap.set(ck, cached);
|
|
170
|
+
this.maybeLogMetrics();
|
|
171
|
+
if (latL0 > this.budgetL0Ms) {
|
|
172
|
+
this.logBudgetWarn("L0", latL0, this.budgetL0Ms);
|
|
173
|
+
}
|
|
174
|
+
return { hits: cached.hits, tier: "L0", latencyMs: { l0: latL0 } };
|
|
175
|
+
}
|
|
176
|
+
this.l0Misses++;
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
this.l0Misses++;
|
|
180
|
+
this.logTierThrew("L0", err);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// ---- L1: FTS5 trigram BM25 search ----
|
|
184
|
+
let fts5Hits = [];
|
|
185
|
+
try {
|
|
186
|
+
const t0L1 = Date.now();
|
|
187
|
+
const reader = openStore(this.stateDir);
|
|
188
|
+
fts5Hits = fts5SearchScoped(query, reader, sessionId, k);
|
|
189
|
+
const latL1 = Date.now() - t0L1;
|
|
190
|
+
this.accLatencyL1 += latL1;
|
|
191
|
+
if (latL1 > this.budgetL1Ms) {
|
|
192
|
+
this.logBudgetWarn("L1", latL1, this.budgetL1Ms);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
this.logTierThrew("L1", err);
|
|
197
|
+
// Fall through to L2.
|
|
198
|
+
}
|
|
199
|
+
const l1Score = fts5Hits.length > 0 ? fts5Hits[0].score : 0;
|
|
200
|
+
const l1HighEnough = fts5Hits.length > 0 &&
|
|
201
|
+
this.fts5MaxBm25 != null &&
|
|
202
|
+
l1Score >= this.l1ConfidenceFraction * this.fts5MaxBm25;
|
|
203
|
+
if (l1HighEnough && fts5Hits.length >= k) {
|
|
204
|
+
this.l1Hits++;
|
|
205
|
+
const hits = await this.hydrateHits(fts5Hits.slice(0, k), sessionId);
|
|
206
|
+
this.cacheResult(ck, hits, "L1");
|
|
207
|
+
this.maybeLogMetrics();
|
|
208
|
+
return {
|
|
209
|
+
hits,
|
|
210
|
+
tier: "L1",
|
|
211
|
+
latencyMs: { l0: 0, l1: 0 },
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
if (fts5Hits.length > 0) {
|
|
215
|
+
this.l1Misses++; // L1 returned results but insufficient confidence
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
this.l1Misses++;
|
|
219
|
+
}
|
|
220
|
+
// ---- L2: PGlite HNSW vector search ----
|
|
221
|
+
try {
|
|
222
|
+
const t0L2 = Date.now();
|
|
223
|
+
const asyncHits = await vectorSearchAsync(store, sessionId ?? "", query, k);
|
|
224
|
+
const latL2 = Date.now() - t0L2;
|
|
225
|
+
this.accLatencyL2 += latL2;
|
|
226
|
+
if (asyncHits.length > 0) {
|
|
227
|
+
this.l2Hits++;
|
|
228
|
+
const hits = asyncHits.slice(0, k);
|
|
229
|
+
this.cacheResult(ck, hits, "L2");
|
|
230
|
+
this.maybeLogMetrics();
|
|
231
|
+
if (latL2 > this.budgetL2Ms) {
|
|
232
|
+
this.logBudgetWarn("L2", latL2, this.budgetL2Ms);
|
|
233
|
+
}
|
|
234
|
+
return { hits, tier: "L2", latencyMs: { l2: latL2 } };
|
|
235
|
+
}
|
|
236
|
+
this.l2Misses++;
|
|
237
|
+
}
|
|
238
|
+
catch (err) {
|
|
239
|
+
this.l2Misses++;
|
|
240
|
+
this.logTierThrew("L2", err);
|
|
241
|
+
// Fall through -- caller should fall back to sync searchRecall().
|
|
242
|
+
}
|
|
243
|
+
this.maybeLogMetrics();
|
|
244
|
+
return { hits: [], tier: "L2", latencyMs: {} };
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Synchronous L0 cache peek. Returns cached hits for (sessionId, query, k)
|
|
248
|
+
* and promotes the entry to the front of the LRU, or null on miss. Use this
|
|
249
|
+
* from synchronous recall paths that cannot await route() (route() is async).
|
|
250
|
+
* Does not touch L1/L2 — only the in-memory LRU.
|
|
251
|
+
*/
|
|
252
|
+
peekCache(sessionId, query, k) {
|
|
253
|
+
const ck = cacheKey(sessionId, query, k);
|
|
254
|
+
const cached = this.cacheMap.get(ck);
|
|
255
|
+
if (!cached)
|
|
256
|
+
return null;
|
|
257
|
+
this.l0Hits++;
|
|
258
|
+
this.cacheMap.delete(ck);
|
|
259
|
+
this.cacheMap.set(ck, cached); // promote to front
|
|
260
|
+
return cached.hits;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Invalidate cached results for a given session.
|
|
264
|
+
* Called when new messages arrive (session context changed).
|
|
265
|
+
*/
|
|
266
|
+
invalidateSession(sessionId) {
|
|
267
|
+
if (sessionId) {
|
|
268
|
+
const prefix = `${sessionId}||`;
|
|
269
|
+
for (const [key] of this.cacheMap) {
|
|
270
|
+
// cacheKey format: <sessionId>||<query>||<k>
|
|
271
|
+
if (key.startsWith(prefix)) {
|
|
272
|
+
this.cacheMap.delete(key);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
// Cross-session entries start with "__cross__||"
|
|
278
|
+
for (const [key] of this.cacheMap) {
|
|
279
|
+
if (key.startsWith("__cross__||")) {
|
|
280
|
+
this.cacheMap.delete(key);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/** Force-clear the entire result cache. */
|
|
286
|
+
clearCache() {
|
|
287
|
+
this.cacheMap.clear();
|
|
288
|
+
this.embeddingCache.clear();
|
|
289
|
+
}
|
|
290
|
+
/** Snapshot of current metrics for dashboard / logging. */
|
|
291
|
+
getMetrics() {
|
|
292
|
+
const q = Math.max(this.totalQueries, 1);
|
|
293
|
+
return {
|
|
294
|
+
l0Hits: this.l0Hits,
|
|
295
|
+
l0Misses: this.l0Misses,
|
|
296
|
+
l1Hits: this.l1Hits,
|
|
297
|
+
l1Misses: this.l1Misses,
|
|
298
|
+
l2Hits: this.l2Hits,
|
|
299
|
+
l2Misses: this.l2Misses,
|
|
300
|
+
totalQueries: this.totalQueries,
|
|
301
|
+
avgLatencyMs: {
|
|
302
|
+
l0: Math.round((this.accLatencyL0 / q) * 100) / 100,
|
|
303
|
+
l1: Math.round((this.accLatencyL1 / q) * 100) / 100,
|
|
304
|
+
l2: Math.round((this.accLatencyL2 / q) * 100) / 100,
|
|
305
|
+
},
|
|
306
|
+
fts5MaxBm25: this.fts5MaxBm25,
|
|
307
|
+
uncalibrated: this.uncalibrated,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
/** Reset all metrics counters (for testing). */
|
|
311
|
+
resetMetrics() {
|
|
312
|
+
this.l0Hits = 0;
|
|
313
|
+
this.l0Misses = 0;
|
|
314
|
+
this.l1Hits = 0;
|
|
315
|
+
this.l1Misses = 0;
|
|
316
|
+
this.l2Hits = 0;
|
|
317
|
+
this.l2Misses = 0;
|
|
318
|
+
this.totalQueries = 0;
|
|
319
|
+
this.accLatencyL0 = 0;
|
|
320
|
+
this.accLatencyL1 = 0;
|
|
321
|
+
this.accLatencyL2 = 0;
|
|
322
|
+
}
|
|
323
|
+
// -----------------------------------------------------------------------
|
|
324
|
+
// Internal helpers
|
|
325
|
+
// -----------------------------------------------------------------------
|
|
326
|
+
/**
|
|
327
|
+
* Hydrate FTS5 hits (which only carry checkpoint `id` + `score`) into
|
|
328
|
+
* full SearchHit objects by fetching from the sync store.
|
|
329
|
+
*/
|
|
330
|
+
async hydrateHits(fts5Hits, sessionId) {
|
|
331
|
+
const cps = listCheckpoints(sessionId ?? "", this.stateDir);
|
|
332
|
+
const cpMap = new Map(cps.map((cp) => [cp.checkpointId, cp]));
|
|
333
|
+
const hits = [];
|
|
334
|
+
for (const fh of fts5Hits) {
|
|
335
|
+
const cp = cpMap.get(fh.id);
|
|
336
|
+
if (cp) {
|
|
337
|
+
hits.push({ checkpoint: cp, score: fh.score });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return hits;
|
|
341
|
+
}
|
|
342
|
+
/** Insert into the LRU cache, evicting oldest entry if over capacity. */
|
|
343
|
+
cacheResult(key, hits, tier) {
|
|
344
|
+
if (this.cacheMap.has(key)) {
|
|
345
|
+
this.cacheMap.delete(key);
|
|
346
|
+
}
|
|
347
|
+
else if (this.cacheMap.size >= this.cacheSize) {
|
|
348
|
+
const oldest = this.cacheMap.keys().next();
|
|
349
|
+
if (oldest.value !== undefined) {
|
|
350
|
+
this.cacheMap.delete(oldest.value);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
this.cacheMap.set(key, { key, hits, tier });
|
|
354
|
+
}
|
|
355
|
+
/** Append a structured event to the tiered-router events log (best-effort). */
|
|
356
|
+
appendLog(ev) {
|
|
357
|
+
try {
|
|
358
|
+
mkdirSync(dirname(this.logPath), { recursive: true });
|
|
359
|
+
appendFileSync(this.logPath, JSON.stringify(ev) + "\n");
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
/* best-effort -- never break extension on log failure */
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
/** Log periodic tiered metrics at the configured cadence. */
|
|
366
|
+
maybeLogMetrics() {
|
|
367
|
+
if (this.totalQueries % this.logCadence !== 0)
|
|
368
|
+
return;
|
|
369
|
+
const m = this.getMetrics();
|
|
370
|
+
const payload = {
|
|
371
|
+
event: "tiered_routing_metrics",
|
|
372
|
+
l0Hits: m.l0Hits,
|
|
373
|
+
l0Misses: m.l0Misses,
|
|
374
|
+
l1Hits: m.l1Hits,
|
|
375
|
+
l1Misses: m.l1Misses,
|
|
376
|
+
l2Hits: m.l2Hits,
|
|
377
|
+
l2Misses: m.l2Misses,
|
|
378
|
+
avgLatencyMs: m.avgLatencyMs,
|
|
379
|
+
totalQueries: m.totalQueries,
|
|
380
|
+
fts5MaxBm25: m.fts5MaxBm25,
|
|
381
|
+
uncalibrated: m.uncalibrated,
|
|
382
|
+
};
|
|
383
|
+
if (m.uncalibrated) {
|
|
384
|
+
payload.note = "run scripts/calibrate-fts5.mjs";
|
|
385
|
+
}
|
|
386
|
+
this.appendLog(payload);
|
|
387
|
+
}
|
|
388
|
+
/** Log a tier-threw event (non-fatal fall-through). */
|
|
389
|
+
logTierThrew(tier, err) {
|
|
390
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
391
|
+
this.appendLog({ event: "tier_threw", tier, error: message });
|
|
392
|
+
}
|
|
393
|
+
/** Log a budget-warning event. */
|
|
394
|
+
logBudgetWarn(tier, actualMs, budgetMs) {
|
|
395
|
+
this.appendLog({ event: "tier_budget", tier, actualMs, budgetMs });
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
// ---------------------------------------------------------------------------
|
|
399
|
+
// Factory / singleton
|
|
400
|
+
// ---------------------------------------------------------------------------
|
|
401
|
+
let _defaultRouter = null;
|
|
402
|
+
/**
|
|
403
|
+
* Get or create the singleton tiered router.
|
|
404
|
+
* Feature-gated on TIERED_ROUTING_ENABLED (default true).
|
|
405
|
+
* Returns null when the flag is OFF, so callers can fall through cleanly.
|
|
406
|
+
*/
|
|
407
|
+
export function getTieredRouter() {
|
|
408
|
+
const enabled = envBool("MEGACOMPACT_TIERED_ROUTING_ENABLED", true);
|
|
409
|
+
if (!enabled)
|
|
410
|
+
return null;
|
|
411
|
+
if (!_defaultRouter) {
|
|
412
|
+
_defaultRouter = new TieredRouter({
|
|
413
|
+
stateDir: getStateDir(),
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
return _defaultRouter;
|
|
417
|
+
}
|
|
418
|
+
/** Reset the singleton (for testing). */
|
|
419
|
+
export function resetTieredRouter() {
|
|
420
|
+
_defaultRouter = null;
|
|
421
|
+
}
|
package/dist/src/vectorStore.js
CHANGED
|
@@ -75,7 +75,7 @@ export class VectorStore {
|
|
|
75
75
|
openBloom(this.stateDir);
|
|
76
76
|
}
|
|
77
77
|
/** Emit a structured dedup-decision event (best-effort, never throws). */
|
|
78
|
-
record(tier, result, reason, latencyMs) {
|
|
78
|
+
record(tier, result, reason, latencyMs, similarityScore, matchedId) {
|
|
79
79
|
if (!this.eventsPath)
|
|
80
80
|
return;
|
|
81
81
|
logDecision(this.eventsPath, {
|
|
@@ -84,6 +84,8 @@ export class VectorStore {
|
|
|
84
84
|
result,
|
|
85
85
|
reason,
|
|
86
86
|
latencyMs: Math.round(latencyMs * 100) / 100,
|
|
87
|
+
similarityScore,
|
|
88
|
+
matchedId,
|
|
87
89
|
});
|
|
88
90
|
}
|
|
89
91
|
/**
|
|
@@ -137,7 +139,7 @@ export class VectorStore {
|
|
|
137
139
|
deduped: true,
|
|
138
140
|
reason: "contentHash",
|
|
139
141
|
};
|
|
140
|
-
this.record("L0", "deduped", "contentHash", Date.now() - t0);
|
|
142
|
+
this.record("L0", "deduped", "contentHash", Date.now() - t0, 1, contentMatch.checkpointId);
|
|
141
143
|
onTier?.({ tier: "L0", status: "deduped", detail: "contentHash" });
|
|
142
144
|
return r;
|
|
143
145
|
}
|
|
@@ -159,7 +161,7 @@ export class VectorStore {
|
|
|
159
161
|
deduped: true,
|
|
160
162
|
reason: "regionHash",
|
|
161
163
|
};
|
|
162
|
-
this.record("L0", "deduped", "regionHash", Date.now() - t0);
|
|
164
|
+
this.record("L0", "deduped", "regionHash", Date.now() - t0, 1, regionMatch.checkpointId);
|
|
163
165
|
onTier?.({ tier: "L0", status: "deduped", detail: "regionHash" });
|
|
164
166
|
return r;
|
|
165
167
|
}
|
|
@@ -187,7 +189,7 @@ export class VectorStore {
|
|
|
187
189
|
deduped: true,
|
|
188
190
|
reason: "summaryHash",
|
|
189
191
|
};
|
|
190
|
-
this.record("L0", "deduped", "summaryHash", Date.now() - t0);
|
|
192
|
+
this.record("L0", "deduped", "summaryHash", Date.now() - t0, 1, summaryMatch.checkpointId);
|
|
191
193
|
onTier?.({ tier: "L0", status: "deduped", detail: "summaryHash" });
|
|
192
194
|
return r;
|
|
193
195
|
}
|
|
@@ -207,7 +209,7 @@ export class VectorStore {
|
|
|
207
209
|
upsertCheckpoint(l1, this.stateDir);
|
|
208
210
|
bumpDedupStats(true, this.stateDir);
|
|
209
211
|
const r = { checkpoint: l1, deduped: true, reason: "l1MinHash" };
|
|
210
|
-
this.record("L1", "deduped", "l1MinHash", Date.now() - t0);
|
|
212
|
+
this.record("L1", "deduped", "l1MinHash", Date.now() - t0, 1, l1.checkpointId);
|
|
211
213
|
onTier?.({ tier: "L1", status: "deduped", detail: "l1MinHash" });
|
|
212
214
|
return r;
|
|
213
215
|
}
|
|
@@ -250,7 +252,7 @@ export class VectorStore {
|
|
|
250
252
|
deduped: true,
|
|
251
253
|
reason: "contentSimilarity",
|
|
252
254
|
};
|
|
253
|
-
this.record("L2", "deduped", "contentSimilarity", Date.now() - t0);
|
|
255
|
+
this.record("L2", "deduped", "contentSimilarity", Date.now() - t0, nearest.sim, nearest.checkpoint.checkpointId);
|
|
254
256
|
onTier?.({
|
|
255
257
|
tier: "L2",
|
|
256
258
|
status: "deduped",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as o,j as n}from"./index-
|
|
2
|
-
//# sourceMappingURL=AchievementTiles-
|
|
1
|
+
import{r as o,j as n}from"./index-DrktNOoT.js";function x(s){try{return new Date(s*1e3).toLocaleString()}catch{return"—"}}function f({achievements:s}){const i=o.useRef(0),[r,a]=o.useState(null),l=s.reduce((e,t)=>Math.max(e,t.unlocked_at??0),0),c=i.current;o.useEffect(()=>{l>i.current&&(i.current=l)},[l]),o.useEffect(()=>{if(c>0&&l>c){const e=s.filter(t=>t.unlocked_at!=null&&(t.unlocked_at??0)>c);if(e.length>0){const t=e.map(d=>`${d.icon??""} ${d.title}`.trim()).join(", ")+" unlocked!";a(t);const h=setTimeout(()=>a(null),4e3);return()=>clearTimeout(h)}}},[s,l,c]);const u=s.filter(e=>!(e.hidden===1&&e.unlocked_at==null));return n.jsxs("div",{className:"ach-section",children:[r!=null&&n.jsx("div",{className:"ach-toast show",children:r}),n.jsxs("div",{className:"ach-tiles",children:[u.length===0&&n.jsx("span",{className:"repo-none",children:"no achievements yet"}),u.map(e=>{if(e.unlocked_at!=null){const t=c>0&&(e.unlocked_at??0)>c;return n.jsxs("div",{className:`ach-tile unlocked${t?" just-unlocked":""}`,children:[e.icon??""," ",e.title,n.jsxs("span",{className:"ach-detail",children:["unlocked ",x(e.unlocked_at)]})]},e.id)}return n.jsxs("div",{className:"ach-tile locked",children:["??? ",e.title]},e.id)})]})]})}export{f as A};
|
|
2
|
+
//# sourceMappingURL=AchievementTiles-DR9cLGDL.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AchievementTiles-
|
|
1
|
+
{"version":3,"file":"AchievementTiles-DR9cLGDL.js","sources":["../../src/components/AchievementTiles.tsx"],"sourcesContent":["/**\n * dashboard-client/src/components/AchievementTiles.tsx — shared achievement grid.\n *\n * Renders a grid of achievement tiles from fetchAchievements() data.\n * Tile states: unlocked (icon+title+date), visible-locked (??? teaser),\n * hidden+locked (not rendered). Just-unlocked tiles get a pulse animation.\n * Toast notification fires for newly-unlocked achievements.\n *\n * Used by both GameTab (sub-section) and AchievementsTab (standalone).\n */\n\nimport type React from \"react\";\nimport { useRef, useState, useEffect } from \"react\";\nimport type { AchievementRow } from \"@contracts\";\n\nexport interface AchievementTilesProps {\n\t/** Achievement rows from /api/achievements. */\n\tachievements: AchievementRow[];\n}\n\n/** Format unlock timestamp (seconds → locale string). */\nfunction fmtUnlockDate(ts: number): string {\n\ttry {\n\t\treturn new Date(ts * 1000).toLocaleString();\n\t} catch {\n\t\treturn \"\\u2014\";\n\t}\n}\n\nexport function AchievementTiles({\n\tachievements,\n}: AchievementTilesProps): React.ReactElement {\n\tconst lastMaxTsRef = useRef(0);\n\tconst [toastText, setToastText] = useState<string | null>(null);\n\n\tconst maxTs = achievements.reduce(\n\t\t(mx, a) => Math.max(mx, a.unlocked_at ?? 0),\n\t\t0,\n\t);\n\tconst prevMaxTs = lastMaxTsRef.current;\n\n\t/* Update ref after render so next poll sees previous baseline. */\n\tuseEffect(() => {\n\t\tif (maxTs > lastMaxTsRef.current) {\n\t\t\tlastMaxTsRef.current = maxTs;\n\t\t}\n\t}, [maxTs]);\n\n\t/* Toast for newly-unlocked achievements. */\n\tuseEffect(() => {\n\t\tif (prevMaxTs > 0 && maxTs > prevMaxTs) {\n\t\t\tconst newly = achievements.filter(\n\t\t\t\t(a) => a.unlocked_at != null && (a.unlocked_at ?? 0) > prevMaxTs,\n\t\t\t);\n\t\t\tif (newly.length > 0) {\n\t\t\t\tconst text =\n\t\t\t\t\tnewly\n\t\t\t\t\t\t.map((a) => `${a.icon ?? \"\"} ${a.title}`.trim())\n\t\t\t\t\t\t.join(\", \") + \" unlocked!\";\n\t\t\t\tsetToastText(text);\n\t\t\t\tconst t = setTimeout(() => setToastText(null), 4000);\n\t\t\t\treturn (): void => clearTimeout(t);\n\t\t\t}\n\t\t}\n\t}, [achievements, maxTs, prevMaxTs]);\n\n\t/* Visible tiles: skip hidden+locked (hidden === 1 && unlocked_at == null). */\n\tconst tiles = achievements.filter(\n\t\t(a) => !(a.hidden === 1 && a.unlocked_at == null),\n\t);\n\n\treturn (\n\t\t<div className=\"ach-section\">\n\t\t\t{toastText != null && (\n\t\t\t\t<div className=\"ach-toast show\">{toastText}</div>\n\t\t\t)}\n\t\t\t<div className=\"ach-tiles\">\n\t\t\t\t{tiles.length === 0 && (\n\t\t\t\t\t<span className=\"repo-none\">no achievements yet</span>\n\t\t\t\t)}\n\t\t\t\t{tiles.map((a) => {\n\t\t\t\t\tif (a.unlocked_at != null) {\n\t\t\t\t\t\tconst isNew = prevMaxTs > 0 && (a.unlocked_at ?? 0) > prevMaxTs;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tkey={a.id}\n\t\t\t\t\t\t\t\tclassName={`ach-tile unlocked${isNew ? \" just-unlocked\" : \"\"}`}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{a.icon ?? \"\"} {a.title}\n\t\t\t\t\t\t\t\t<span className=\"ach-detail\">\n\t\t\t\t\t\t\t\t\tunlocked {fmtUnlockDate(a.unlocked_at)}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<div key={a.id} className=\"ach-tile locked\">\n\t\t\t\t\t\t\t??? {a.title}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t);\n\t\t\t\t})}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"],"names":["fmtUnlockDate","ts","AchievementTiles","achievements","lastMaxTsRef","useRef","toastText","setToastText","useState","maxTs","mx","a","prevMaxTs","useEffect","newly","text","t","tiles","jsxs","jsx","isNew"],"mappings":"+CAqBA,SAASA,EAAcC,EAAoB,CAC1C,GAAI,CACH,OAAO,IAAI,KAAKA,EAAK,GAAI,EAAE,eAAA,CAC5B,MAAQ,CACP,MAAO,GACR,CACD,CAEO,SAASC,EAAiB,CAChC,aAAAC,CACD,EAA8C,CAC7C,MAAMC,EAAeC,EAAAA,OAAO,CAAC,EACvB,CAACC,EAAWC,CAAY,EAAIC,EAAAA,SAAwB,IAAI,EAExDC,EAAQN,EAAa,OAC1B,CAACO,EAAIC,IAAM,KAAK,IAAID,EAAIC,EAAE,aAAe,CAAC,EAC1C,CAAA,EAEKC,EAAYR,EAAa,QAG/BS,EAAAA,UAAU,IAAM,CACXJ,EAAQL,EAAa,UACxBA,EAAa,QAAUK,EAEzB,EAAG,CAACA,CAAK,CAAC,EAGVI,EAAAA,UAAU,IAAM,CACf,GAAID,EAAY,GAAKH,EAAQG,EAAW,CACvC,MAAME,EAAQX,EAAa,OACzBQ,GAAMA,EAAE,aAAe,OAASA,EAAE,aAAe,GAAKC,CAAA,EAExD,GAAIE,EAAM,OAAS,EAAG,CACrB,MAAMC,EACLD,EACE,IAAKH,GAAM,GAAGA,EAAE,MAAQ,EAAE,IAAIA,EAAE,KAAK,GAAG,KAAA,CAAM,EAC9C,KAAK,IAAI,EAAI,aAChBJ,EAAaQ,CAAI,EACjB,MAAMC,EAAI,WAAW,IAAMT,EAAa,IAAI,EAAG,GAAI,EACnD,MAAO,IAAY,aAAaS,CAAC,CAClC,CACD,CACD,EAAG,CAACb,EAAcM,EAAOG,CAAS,CAAC,EAGnC,MAAMK,EAAQd,EAAa,OACzBQ,GAAM,EAAEA,EAAE,SAAW,GAAKA,EAAE,aAAe,KAAA,EAG7C,OACCO,EAAAA,KAAC,MAAA,CAAI,UAAU,cACb,SAAA,CAAAZ,GAAa,MACba,EAAAA,IAAC,MAAA,CAAI,UAAU,iBAAkB,SAAAb,EAAU,EAE5CY,EAAAA,KAAC,MAAA,CAAI,UAAU,YACb,SAAA,CAAAD,EAAM,SAAW,GACjBE,MAAC,OAAA,CAAK,UAAU,YAAY,SAAA,sBAAmB,EAE/CF,EAAM,IAAKN,GAAM,CACjB,GAAIA,EAAE,aAAe,KAAM,CAC1B,MAAMS,EAAQR,EAAY,IAAMD,EAAE,aAAe,GAAKC,EACtD,OACCM,EAAAA,KAAC,MAAA,CAEA,UAAW,oBAAoBE,EAAQ,iBAAmB,EAAE,GAE3D,SAAA,CAAAT,EAAE,MAAQ,GAAG,IAAEA,EAAE,MAClBO,EAAAA,KAAC,OAAA,CAAK,UAAU,aAAa,SAAA,CAAA,YAClBlB,EAAcW,EAAE,WAAW,CAAA,CAAA,CACtC,CAAA,CAAA,EANKA,EAAE,EAAA,CASV,CACA,OACCO,EAAAA,KAAC,MAAA,CAAe,UAAU,kBAAkB,SAAA,CAAA,OACtCP,EAAE,KAAA,CAAA,EADEA,EAAE,EAEZ,CAEF,CAAC,CAAA,CAAA,CACF,CAAA,EACD,CAEF"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as t,
|
|
2
|
-
//# sourceMappingURL=AchievementsTab-
|
|
1
|
+
import{r as t,n as m,j as s}from"./index-DrktNOoT.js";import{A as v}from"./AchievementTiles-DR9cLGDL.js";function x(){const[r,o]=t.useState([]),[l,u]=t.useState(!0),[a,n]=t.useState(null),c=t.useCallback(async()=>{try{const e=await m();o(e),n(null)}catch(e){n(e instanceof Error?e:new Error(String(e)))}finally{u(!1)}},[]);return t.useEffect(()=>{let e=!0;const i=async()=>{e&&await c()};i();const h=setInterval(()=>void i(),15e3);return()=>{e=!1,clearInterval(h)}},[c]),l&&r.length===0?s.jsx("div",{className:"tab-stub",children:"Loading achievements…"}):a&&r.length===0?s.jsxs("div",{className:"tab-stub",children:["Error: ",a.message]}):s.jsxs("div",{className:"achievements-tab",children:[s.jsx("h2",{children:"Achievements"}),s.jsx(v,{achievements:r})]})}export{x as default};
|
|
2
|
+
//# sourceMappingURL=AchievementsTab-svNUgJ1e.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AchievementsTab-
|
|
1
|
+
{"version":3,"file":"AchievementsTab-svNUgJ1e.js","sources":["../../src/tabs/AchievementsTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/tabs/AchievementsTab.tsx — Achievements tab (NEW).\n *\n * Renders the shared AchievementTiles component standalone, plus a toast area.\n * Polls /api/achievements every 15s.\n */\n\nimport type React from \"react\";\nimport { useState, useEffect, useCallback } from \"react\";\nimport type { AchievementRow } from \"@contracts\";\nimport { fetchAchievements } from \"../api/client\";\nimport { AchievementTiles } from \"../components/AchievementTiles\";\n\nexport default function AchievementsTab(): React.ReactElement {\n\tconst [achievements, setAchievements] = useState<AchievementRow[]>([]);\n\tconst [loading, setLoading] = useState(true);\n\tconst [error, setError] = useState<Error | null>(null);\n\n\tconst doFetch = useCallback(async (): Promise<void> => {\n\t\ttry {\n\t\t\tconst rows = await fetchAchievements();\n\t\t\tsetAchievements(rows);\n\t\t\tsetError(null);\n\t\t} catch (e) {\n\t\t\tsetError(e instanceof Error ? e : new Error(String(e)));\n\t\t} finally {\n\t\t\tsetLoading(false);\n\t\t}\n\t}, []);\n\n\tuseEffect(() => {\n\t\tlet active = true;\n\t\tconst run = async (): Promise<void> => {\n\t\t\tif (active) await doFetch();\n\t\t};\n\t\tvoid run();\n\t\tconst timer = setInterval(() => void run(), 15000);\n\t\treturn () => {\n\t\t\tactive = false;\n\t\t\tclearInterval(timer);\n\t\t};\n\t}, [doFetch]);\n\n\tif (loading && achievements.length === 0)\n\t\treturn <div className=\"tab-stub\">Loading achievements…</div>;\n\tif (error && achievements.length === 0)\n\t\treturn <div className=\"tab-stub\">Error: {error.message}</div>;\n\n\treturn (\n\t\t<div className=\"achievements-tab\">\n\t\t\t<h2>Achievements</h2>\n\t\t\t<AchievementTiles achievements={achievements} />\n\t\t</div>\n\t);\n}\n"],"names":["AchievementsTab","achievements","setAchievements","useState","loading","setLoading","error","setError","doFetch","useCallback","rows","fetchAchievements","useEffect","active","run","timer","jsx","jsxs","AchievementTiles"],"mappings":"yGAaA,SAAwBA,GAAsC,CAC7D,KAAM,CAACC,EAAcC,CAAe,EAAIC,EAAAA,SAA2B,CAAA,CAAE,EAC/D,CAACC,EAASC,CAAU,EAAIF,EAAAA,SAAS,EAAI,EACrC,CAACG,EAAOC,CAAQ,EAAIJ,EAAAA,SAAuB,IAAI,EAE/CK,EAAUC,EAAAA,YAAY,SAA2B,CACtD,GAAI,CACH,MAAMC,EAAO,MAAMC,EAAA,EACnBT,EAAgBQ,CAAI,EACpBH,EAAS,IAAI,CACd,OAAS,EAAG,CACXA,EAAS,aAAa,MAAQ,EAAI,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC,CACvD,QAAA,CACCF,EAAW,EAAK,CACjB,CACD,EAAG,CAAA,CAAE,EAeL,OAbAO,EAAAA,UAAU,IAAM,CACf,IAAIC,EAAS,GACb,MAAMC,EAAM,SAA2B,CAClCD,SAAcL,EAAA,CACnB,EACKM,EAAA,EACL,MAAMC,EAAQ,YAAY,IAAM,KAAKD,EAAA,EAAO,IAAK,EACjD,MAAO,IAAM,CACZD,EAAS,GACT,cAAcE,CAAK,CACpB,CACD,EAAG,CAACP,CAAO,CAAC,EAERJ,GAAWH,EAAa,SAAW,EAC/Be,EAAAA,IAAC,MAAA,CAAI,UAAU,WAAW,SAAA,wBAAqB,EACnDV,GAASL,EAAa,SAAW,EAC7BgB,EAAAA,KAAC,MAAA,CAAI,UAAU,WAAW,SAAA,CAAA,UAAQX,EAAM,OAAA,EAAQ,EAGvDW,EAAAA,KAAC,MAAA,CAAI,UAAU,mBACd,SAAA,CAAAD,EAAAA,IAAC,MAAG,SAAA,cAAA,CAAY,EAChBA,MAACE,GAAiB,aAAAjB,CAAA,CAA4B,CAAA,EAC/C,CAEF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{j as e,u as x,r as p,k as T,l as R,i as $,c as y}from"./index-DrktNOoT.js";import{C as L,T as P}from"./TimeSavedCard-CiqCOQuL.js";import{b as d,c as h,d as H,e as M,f as i}from"./format-COjDIVGj.js";import{l as I}from"./pricing-CvE4NllQ.js";function f(a){return a>=80?"ov-stat-value-green":a>=50?"ov-stat-value-yellow":"ov-stat-value-red"}function k(a){return a==null?"":a>=0?"ov-stat-value-green":"ov-stat-value-red"}function r({label:a,value:t,className:l,title:s}){return e.jsxs("div",{className:"ov-stat-row",children:[e.jsx("span",{className:"ov-stat-label",title:s,children:a}),e.jsx("span",{className:`ov-stat-value${l?` ${l}`:""}`,children:t})]})}function E(a){return e.jsxs("div",{className:"card provider-cache-card",children:[e.jsx(r,{label:"Cache Hit Rate",value:`${a.hitPct.toFixed(1)}%`,className:f(a.hitPct)}),e.jsx(r,{label:"Turns Recorded",value:String(a.turnCount)}),e.jsx(r,{label:"Cache Read Tokens",value:d(a.totalCacheRead)}),e.jsx(r,{label:"Cache Write Tokens",value:d(a.totalCacheWrite)}),e.jsx(r,{label:"Input Tokens",value:d(a.totalInput)}),e.jsx(r,{label:"$ Saved (reads)",value:h(a.cacheReadSaved)}),e.jsx(r,{label:"Write Investment",value:h(a.cacheWriteCost)}),e.jsx(r,{label:"Net Saved",value:h(a.netSaved),className:k(a.netSaved)}),e.jsx(r,{label:"Model",value:a.modelLabel??"—"}),e.jsx(r,{label:"Tracked Since",value:H(a.firstTurnAt)}),e.jsx(r,{label:"Last Updated",value:M(a.latestTurnAt)}),a.byModel.length>0&&e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"ov-stat-section-label",children:"Per Model"}),a.byModel.map(t=>{const l=I(t.model),s=l!=null?t.totalCacheRead/1e6*l:null,c=l!=null?t.totalCacheWrite/1e6*l:null;return e.jsxs("div",{className:"ov-stat-group",children:[e.jsx(r,{label:"Model",value:t.model}),e.jsx(r,{label:"Hit Rate",value:`${t.hitPct.toFixed(1)}%`,className:f(t.hitPct)}),e.jsx(r,{label:"Cache Read",value:d(t.totalCacheRead)}),e.jsx(r,{label:"Cache Write",value:d(t.totalCacheWrite)}),e.jsx(r,{label:"Samples",value:String(t.sampleCount)}),e.jsx(r,{label:"$ Saved (reads)",value:s!=null?h(s):"—"}),e.jsx(r,{label:"Write Investment",value:c!=null?h(c):"—"})]},t.model)})]})]})}const W=["ov-bar-green","ov-bar-blue","ov-bar-teal","ov-bar-yellow","ov-bar-gray"],w=["Permanent","Epoch","Topic","Thread","Volatile"],A={good:"ov-text-green",fair:"ov-text-yellow",degraded:"ov-text-orange",poor:"ov-text-red"};function D({data:a}){const t=Math.max(...a.buckets.map(s=>s.count),1),l=A[a.health.label]??"ov-text-gray";return e.jsxs("div",{className:"ov-card",children:[e.jsx("h3",{className:"ov-card-title",children:"Cache Stripe Distribution"}),e.jsxs("div",{className:"ov-stripe-health-summary",children:[e.jsx("span",{className:`ov-stripe-health-badge ${l}`,children:a.health.label}),e.jsx("span",{className:"ov-stripe-health-score",children:i(a.health.score)}),e.jsxs("span",{className:"ov-stripe-churn",children:["Churn: ",i(a.health.churnRate)]}),e.jsxs("span",{className:"ov-stripe-total",children:[a.totalChunks," total chunks"]})]}),e.jsx("div",{className:"ov-stripe-bars",children:a.buckets.map(s=>{const c=t>0?Math.max(0,Math.min(100,s.count/t*100)):0,o=W[s.stripe]??"ov-bar-gray",n=s.label||(w[s.stripe]??`Stripe ${s.stripe}`);return e.jsxs("div",{className:"ov-stripe-row",children:[e.jsx("span",{className:"ov-stripe-label",title:n,children:n}),e.jsx("div",{className:"ov-bar-track",children:e.jsx("div",{className:`ov-bar-fill ${o}`,style:{width:`${c}%`}})}),e.jsx("span",{className:"ov-stripe-count",children:s.count}),e.jsx("span",{className:"ov-stripe-stability",title:`min ${i(s.minStability)} / max ${i(s.maxStability)}`,children:i(s.avgStability)})]},s.stripe)})}),e.jsxs("div",{className:"ov-stripe-footer",children:["Epoch: ",a.epochId??"—","· Dominant tier: ",i(a.health.dominantTier)]})]})}function v(a){return`${a.toFixed(1)}%`}function F(a){return a>=80?"ov-bar-green":a>=60?"ov-bar-teal":a>=40?"ov-bar-yellow":a>=20?"ov-bar-orange":"ov-bar-red"}const _=({perf:a})=>{const{cache_hit_pct:t}=a,l=t.samples&&t.samples.length>0?t.samples:[];return e.jsxs("div",{className:"ov-card cache-trend-card",children:[e.jsx("h3",{className:"ov-card-title",children:"Cache Hit-Rate Trend"}),e.jsxs("div",{className:"ov-perf-summary",children:[e.jsxs("div",{className:"ov-perf-stat",children:["Avg: ",e.jsx("strong",{children:v(t.avg)})]}),e.jsxs("div",{className:"ov-perf-stat",children:["Latest: ",e.jsx("strong",{children:v(t.latest)})]}),e.jsxs("div",{className:"ov-perf-stat",children:["Samples: ",e.jsx("strong",{children:t.n})]})]}),l.length>0?e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"ov-sparkline",role:"img","aria-label":`Cache hit rate trend: ${t.n} samples, average ${v(t.avg)}, latest ${v(t.latest)}`,children:l.map((s,c)=>{const o=Math.max(2,Math.round(s.pct*.6)),n=F(s.pct),b=c===l.length-1;return e.jsx("div",{className:`ov-sparkline-bar ${n}${b?" ov-bar-latest":""}`,style:{height:`${o}px`},title:`${v(s.pct)} at ${new Date(s.ts).toLocaleTimeString()}`},c)})}),e.jsxs("div",{className:"ov-card-footer",children:[l.length," sample",l.length!==1?"s":""," over"," ",l.length>1?`${Math.round((l[l.length-1].ts-l[0].ts)/1e3)}s`:"latest turn"]})]}):e.jsx("div",{className:"ov-empty-state",children:"No cache-hit samples yet"})]})};function q(){const{data:a,loading:t,error:l}=x(p.useCallback(()=>T(),[]),{pollInterval:5e3}),{data:s,loading:c,error:o}=x(p.useCallback(()=>R(),[]),{pollInterval:15e3}),{data:n,loading:b,error:j}=x(p.useCallback(()=>$({minutes:30}),[]),{pollInterval:15e3}),{data:g,loading:N,error:C}=x(p.useCallback(()=>y(),[]),{pollInterval:5e3});return e.jsxs("div",{className:"cache-tab",children:[e.jsx("h2",{children:"Provider Prompt Cache"}),t?e.jsx("p",{className:"tab-stub",children:"Loading..."}):l?e.jsxs("p",{className:"tab-stub",children:["Provider cache unavailable: ",l.message]}):a?e.jsx(E,{hitPct:a.cache.avgHitPct,turnCount:a.cache.turnCount,totalCacheRead:a.cache.totalCacheRead,totalCacheWrite:a.cache.totalCacheWrite,totalInput:a.cache.totalInput,cacheReadSaved:a.savings?.cacheReadSaved??null,cacheWriteCost:a.savings?.cacheWriteCost??null,netSaved:a.savings?.netSaved??null,modelLabel:a.savings?.model??null,firstTurnAt:a.cache.firstTurnAt,latestTurnAt:a.cache.latestTurnAt,byModel:a.cache.byModel??[]}):null,e.jsx("h2",{children:"Cache Stripe Distribution"}),c?e.jsx("p",{className:"tab-stub",children:"Loading..."}):o?e.jsxs("p",{className:"tab-stub",children:["Cache stripe data unavailable: ",o.message]}):s?e.jsx(D,{data:s}):null,e.jsx("h2",{children:"Cache Hit-Rate Trend"}),b?e.jsx("p",{className:"tab-stub",children:"Loading..."}):j?e.jsxs("p",{className:"tab-stub",children:["Hit-rate trend unavailable: ",j.message]}):n?e.jsx(_,{perf:n}):null,e.jsx("h2",{children:"Mega-Compact Dedup Cache"}),N?e.jsx("p",{className:"tab-stub",children:"Loading..."}):C?e.jsxs("p",{className:"tab-stub",children:["Snapshot unavailable: ",C.message]}):g&&(()=>{const{cacheHits:u,compacts:S,timeSaved:m}=g;return e.jsxs("div",{className:"card-grid overview-card-grid",children:[e.jsx(L,{cacheHitsSession:u.session,cacheHitsTotal:u.total,tokensSavedSession:u.sessionTokensSaved,tokensSavedTotal:u.totalTokensSaved,compactionsSession:S.session,compactionsTotal:S.total}),e.jsx(P,{compactSessionSec:m.compact.sessionSec,compactTotalSec:m.compact.totalSec,cacheHitSessionSec:m.cacheHit.sessionSec,cacheHitTotalSec:m.cacheHit.totalSec})]})})()]})}export{q as default};
|
|
2
|
+
//# sourceMappingURL=CacheTab-ASwhOMjR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CacheTab-ASwhOMjR.js","sources":["../../src/components/ProviderCacheCard.tsx","../../src/components/StripeDistributionCard.tsx","../../src/components/CacheHitRateTrendCard.tsx","../../src/tabs/CacheTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/components/ProviderCacheCard.tsx — Provider prompt cache stats card.\n *\n * Displays lifetime provider prompt cache hit-rate aggregates + estimated\n * dollar savings from the /api/provider-cache endpoint.\n * Reuses the `ov-stat-row` / `StatRow` pattern from CacheHitsCard.tsx.\n *\n * B.2 spec: Cache Hit Rate with color classes, humanized token counts,\n * priced $ rows with green/red net, model name, locale date + relative time.\n */\n\nimport type React from \"react\";\nimport {\n\tfmtTokens,\n\tfmtDollars,\n\tfmtDate,\n\tfmtRelativeTime,\n} from \"../utils/format\";\nimport { lookupModelInputRate } from \"@pricing\";\n\n// ---------------------------------------------------------------------------\n// Per-model breakdown type (mirrors the API contract).\n// ---------------------------------------------------------------------------\n\nexport interface ByModelEntry {\n\tmodel: string;\n\thitPct: number;\n\ttotalCacheRead: number;\n\ttotalCacheWrite: number;\n\tsampleCount: number;\n}\n\n// ---------------------------------------------------------------------------\n// Flattened props — destructured from ProviderCacheResponse in CacheTab.\n// ---------------------------------------------------------------------------\n\nexport interface ProviderCacheCardProps {\n\t/** Cache hit rate percentage (0–100). */\n\thitPct: number;\n\t/** Number of turns with cache_hit_pct samples. */\n\tturnCount: number;\n\t/** Total cache-read tokens across all sampled turns. */\n\ttotalCacheRead: number;\n\t/** Total cache-write tokens across all sampled turns. */\n\ttotalCacheWrite: number;\n\t/** Total input tokens across all sampled turns. */\n\ttotalInput: number;\n\t/** $ saved from cache reads (null when unpriced). */\n\tcacheReadSaved: number | null;\n\t/** $ spent on cache writes (null when unpriced). */\n\tcacheWriteCost: number | null;\n\t/** netSaved = cacheReadSaved - cacheWriteCost (null when unpriced). */\n\tnetSaved: number | null;\n\t/** Model display name (modelName || modelId, or null). */\n\tmodelLabel: string | null;\n\t/** ISO timestamp of first recorded turn. */\n\tfirstTurnAt: string | null;\n\t/** ISO timestamp of latest recorded turn. */\n\tlatestTurnAt: string | null;\n\t/** Per-model breakdown (F4). */\n\tbyModel: ByModelEntry[];\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Hit-rate color: green ≥80, yellow ≥50, red <50. */\nfunction hitPctClass(pct: number): string {\n\tif (pct >= 80) return \"ov-stat-value-green\";\n\tif (pct >= 50) return \"ov-stat-value-yellow\";\n\treturn \"ov-stat-value-red\";\n}\n\n/** Net-saved color: green positive, red negative. */\nfunction netClass(v: number | null): string {\n\tif (v == null) return \"\";\n\treturn v >= 0 ? \"ov-stat-value-green\" : \"ov-stat-value-red\";\n}\n\nfunction StatRow({\n\tlabel,\n\tvalue,\n\tclassName,\n\ttitle,\n}: {\n\tlabel: string;\n\tvalue: string;\n\tclassName?: string;\n\ttitle?: string;\n}): React.ReactElement {\n\treturn (\n\t\t<div className=\"ov-stat-row\">\n\t\t\t<span className=\"ov-stat-label\" title={title}>\n\t\t\t\t{label}\n\t\t\t</span>\n\t\t\t<span className={`ov-stat-value${className ? ` ${className}` : \"\"}`}>\n\t\t\t\t{value}\n\t\t\t</span>\n\t\t</div>\n\t);\n}\n\n// ---------------------------------------------------------------------------\n// Card\n// ---------------------------------------------------------------------------\n\nexport function ProviderCacheCard(\n\tprops: ProviderCacheCardProps,\n): React.ReactElement {\n\treturn (\n\t\t<div className=\"card provider-cache-card\">\n\t\t\t{/* --- Hit Rate + Turns --- */}\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Cache Hit Rate\"\n\t\t\t\tvalue={`${props.hitPct.toFixed(1)}%`}\n\t\t\t\tclassName={hitPctClass(props.hitPct)}\n\t\t\t/>\n\t\t\t<StatRow label=\"Turns Recorded\" value={String(props.turnCount)} />\n\n\t\t\t{/* --- Token counts --- */}\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Cache Read Tokens\"\n\t\t\t\tvalue={fmtTokens(props.totalCacheRead)}\n\t\t\t/>\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Cache Write Tokens\"\n\t\t\t\tvalue={fmtTokens(props.totalCacheWrite)}\n\t\t\t/>\n\t\t\t<StatRow label=\"Input Tokens\" value={fmtTokens(props.totalInput)} />\n\n\t\t\t{/* --- Dollar rows (null when unpriced) --- */}\n\t\t\t<StatRow\n\t\t\t\tlabel=\"$ Saved (reads)\"\n\t\t\t\tvalue={fmtDollars(props.cacheReadSaved)}\n\t\t\t/>\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Write Investment\"\n\t\t\t\tvalue={fmtDollars(props.cacheWriteCost)}\n\t\t\t/>\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Net Saved\"\n\t\t\t\tvalue={fmtDollars(props.netSaved)}\n\t\t\t\tclassName={netClass(props.netSaved)}\n\t\t\t/>\n\n\t\t\t{/* --- Model + timestamps --- */}\n\t\t\t<StatRow label=\"Model\" value={props.modelLabel ?? \"\\u2014\"} />\n\t\t\t<StatRow label=\"Tracked Since\" value={fmtDate(props.firstTurnAt)} />\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Last Updated\"\n\t\t\t\tvalue={fmtRelativeTime(props.latestTurnAt)}\n\t\t\t/>\n\n\t\t\t{/* --- Per-model breakdown (F4) --- */}\n\t\t\t{props.byModel.length > 0 && (\n\t\t\t\t<>\n\t\t\t\t\t<div className=\"ov-stat-section-label\">Per Model</div>\n\t\t\t\t\t{props.byModel.map((m) => {\n\t\t\t\t\t\tconst rate = lookupModelInputRate(m.model);\n\t\t\t\t\t\tconst cr =\n\t\t\t\t\t\t\trate != null ? (m.totalCacheRead / 1_000_000) * rate : null;\n\t\t\t\t\t\tconst cw =\n\t\t\t\t\t\t\trate != null ? (m.totalCacheWrite / 1_000_000) * rate : null;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<div key={m.model} className=\"ov-stat-group\">\n\t\t\t\t\t\t\t\t<StatRow label=\"Model\" value={m.model} />\n\t\t\t\t\t\t\t\t<StatRow\n\t\t\t\t\t\t\t\t\tlabel=\"Hit Rate\"\n\t\t\t\t\t\t\t\t\tvalue={`${m.hitPct.toFixed(1)}%`}\n\t\t\t\t\t\t\t\t\tclassName={hitPctClass(m.hitPct)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<StatRow\n\t\t\t\t\t\t\t\t\tlabel=\"Cache Read\"\n\t\t\t\t\t\t\t\t\tvalue={fmtTokens(m.totalCacheRead)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<StatRow\n\t\t\t\t\t\t\t\t\tlabel=\"Cache Write\"\n\t\t\t\t\t\t\t\t\tvalue={fmtTokens(m.totalCacheWrite)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<StatRow label=\"Samples\" value={String(m.sampleCount)} />\n\t\t\t\t\t\t\t\t<StatRow\n\t\t\t\t\t\t\t\t\tlabel=\"$ Saved (reads)\"\n\t\t\t\t\t\t\t\t\tvalue={cr != null ? fmtDollars(cr) : \"\\u2014\"}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<StatRow\n\t\t\t\t\t\t\t\t\tlabel=\"Write Investment\"\n\t\t\t\t\t\t\t\t\tvalue={cw != null ? fmtDollars(cw) : \"\\u2014\"}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t);\n\t\t\t\t\t})}\n\t\t\t\t</>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/components/StripeDistributionCard.tsx — Cache stripe\n * distribution visualization (A3, PLAN_V2 Phase 4).\n *\n * Renders a horizontal bar chart of stripe buckets (0–4) with stability\n * indicators, plus a health-score summary box.\n *\n * Uses only CSS-based bar rendering — no external charting dependencies.\n */\n\nimport type React from \"react\";\nimport type { CacheStripesResponse } from \"@contracts\";\nimport { fmtPctFromFraction } from \"../utils/format\";\n\nconst STRIPE_COLORS = [\n\t\"ov-bar-green\", // 0 permanent\n\t\"ov-bar-blue\", // 1 epoch\n\t\"ov-bar-teal\", // 2 topic\n\t\"ov-bar-yellow\", // 3 thread\n\t\"ov-bar-gray\", // 4 volatile\n];\n\nconst STRIPE_LABELS = [\n\t\"Permanent\",\n\t\"Epoch\",\n\t\"Topic\",\n\t\"Thread\",\n\t\"Volatile\",\n];\n\nconst HEALTH_LABEL_COLOR: Record<string, string> = {\n\tgood: \"ov-text-green\",\n\tfair: \"ov-text-yellow\",\n\tdegraded: \"ov-text-orange\",\n\tpoor: \"ov-text-red\",\n};\n\nexport interface StripeDistributionCardProps {\n\tdata: CacheStripesResponse;\n}\n\nexport function StripeDistributionCard({\n\tdata,\n}: StripeDistributionCardProps): React.ReactElement {\n\tconst maxCount = Math.max(...data.buckets.map((b) => b.count), 1);\n\tconst healthColor = HEALTH_LABEL_COLOR[data.health.label] ?? \"ov-text-gray\";\n\n\treturn (\n\t\t<div className=\"ov-card\">\n\t\t\t<h3 className=\"ov-card-title\">Cache Stripe Distribution</h3>\n\n\t\t\t{/* Health score summary */}\n\t\t\t<div className=\"ov-stripe-health-summary\">\n\t\t\t\t<span className={`ov-stripe-health-badge ${healthColor}`}>\n\t\t\t\t\t{data.health.label}\n\t\t\t\t</span>\n\t\t\t\t<span className=\"ov-stripe-health-score\">\n\t\t\t\t\t{fmtPctFromFraction(data.health.score)}\n\t\t\t\t</span>\n\t\t\t\t<span className=\"ov-stripe-churn\">\n\t\t\t\t\tChurn: {fmtPctFromFraction(data.health.churnRate)}\n\t\t\t\t</span>\n\t\t\t\t<span className=\"ov-stripe-total\">\n\t\t\t\t\t{data.totalChunks} total chunks\n\t\t\t\t</span>\n\t\t\t</div>\n\n\t\t\t{/* Bar chart — one row per stripe */}\n\t\t\t<div className=\"ov-stripe-bars\">\n\t\t\t\t{data.buckets.map((bucket) => {\n\t\t\t\t\tconst pct =\n\t\t\t\t\t\tmaxCount > 0\n\t\t\t\t\t\t\t? Math.max(0, Math.min(100, (bucket.count / maxCount) * 100))\n\t\t\t\t\t\t\t: 0;\n\t\t\t\t\tconst colorClass =\n\t\t\t\t\t\tSTRIPE_COLORS[bucket.stripe] ?? \"ov-bar-gray\";\n\t\t\t\t\tconst label =\n\t\t\t\t\t\tbucket.label || (STRIPE_LABELS[bucket.stripe] ?? `Stripe ${bucket.stripe}`);\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<div key={bucket.stripe} className=\"ov-stripe-row\">\n\t\t\t\t\t\t\t<span className=\"ov-stripe-label\" title={label}>\n\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<div className=\"ov-bar-track\">\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tclassName={`ov-bar-fill ${colorClass}`}\n\t\t\t\t\t\t\t\t\tstyle={{ width: `${pct}%` }}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<span className=\"ov-stripe-count\">{bucket.count}</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclassName=\"ov-stripe-stability\"\n\t\t\t\t\t\t\t\ttitle={`min ${fmtPctFromFraction(bucket.minStability)} / max ${fmtPctFromFraction(bucket.maxStability)}`}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{fmtPctFromFraction(bucket.avgStability)}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t);\n\t\t\t\t})}\n\t\t\t</div>\n\n\t\t\t{/* Epoch info */}\n\t\t\t<div className=\"ov-stripe-footer\">\n\t\t\t\tEpoch: {data.epochId ?? \"—\"}\n\t\t\t\t·\n\t\t\t\tDominant tier: {fmtPctFromFraction(data.health.dominantTier)}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/components/CacheHitRateTrendCard.tsx -- Cache hit-rate\n * time-series trend chart (A3, PLAN_V2 Phase 4).\n *\n * Renders a sparkline view of cache hit rate from the /api/perf endpoint using\n * real cache_hit_pct samples with timestamps. Each bar represents one sample.\n * Shows the latest, average, and sample count.\n *\n * Uses .ov-sparkline / .ov-sparkline-bar CSS classes from cache.css.\n * No external charting dependencies.\n */\n\nimport type React from \"react\";\nimport type { PerfResponse, CacheHitSample } from \"@contracts\";\n\nexport interface CacheHitRateTrendCardProps {\n\tperf: PerfResponse;\n}\n\nfunction fmtPct(n: number): string {\n\treturn `${n.toFixed(1)}%`;\n}\n\n/**\n * Map a percentage (0–100) to a CSS color class:\n * >= 80: green (healthy)\n * >= 60: teal (fair)\n * >= 40: yellow (degraded)\n * >= 20: orange (poor)\n * < 20: red (critical)\n */\nfunction pctColorClass(pct: number): string {\n\tif (pct >= 80) return \"ov-bar-green\";\n\tif (pct >= 60) return \"ov-bar-teal\";\n\tif (pct >= 40) return \"ov-bar-yellow\";\n\tif (pct >= 20) return \"ov-bar-orange\";\n\treturn \"ov-bar-red\";\n}\n\nexport const CacheHitRateTrendCard: React.FC<CacheHitRateTrendCardProps> = ({\n\tperf,\n}) => {\n\tconst { cache_hit_pct: c } = perf;\n\tconst samples: CacheHitSample[] =\n\t\tc.samples && c.samples.length > 0 ? c.samples : [];\n\n\treturn (\n\t\t<div className=\"ov-card cache-trend-card\">\n\t\t\t<h3 className=\"ov-card-title\">Cache Hit-Rate Trend</h3>\n\n\t\t\t{/* Metric summary */}\n\t\t\t<div className=\"ov-perf-summary\">\n\t\t\t\t<div className=\"ov-perf-stat\">\n\t\t\t\t\tAvg: <strong>{fmtPct(c.avg)}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"ov-perf-stat\">\n\t\t\t\t\tLatest: <strong>{fmtPct(c.latest)}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"ov-perf-stat\">\n\t\t\t\t\tSamples: <strong>{c.n}</strong>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{/* Sparkline bars from real samples */}\n\t\t\t{samples.length > 0 ? (\n\t\t\t\t<>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"ov-sparkline\"\n\t\t\t\t\t\trole=\"img\"\n\t\t\t\t\t\taria-label={`Cache hit rate trend: ${c.n} samples, average ${fmtPct(c.avg)}, latest ${fmtPct(c.latest)}`}\n\t\t\t\t\t>\n\t\t\t\t\t\t{samples.map((s, i) => {\n\t\t\t\t\t\t\tconst barH = Math.max(2, Math.round(s.pct * 0.6));\n\t\t\t\t\t\t\tconst colorClass = pctColorClass(s.pct);\n\t\t\t\t\t\t\tconst isLatest = i === samples.length - 1;\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tkey={i}\n\t\t\t\t\t\t\t\t\tclassName={`ov-sparkline-bar ${colorClass}${isLatest ? \" ov-bar-latest\" : \"\"}`}\n\t\t\t\t\t\t\t\t\tstyle={{ height: `${barH}px` }}\n\t\t\t\t\t\t\t\t\ttitle={`${fmtPct(s.pct)} at ${new Date(s.ts).toLocaleTimeString()}`}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t})}\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"ov-card-footer\">\n\t\t\t\t\t\t{samples.length} sample{samples.length !== 1 ? \"s\" : \"\"} over{\" \"}\n\t\t\t\t\t\t{samples.length > 1\n\t\t\t\t\t\t\t? `${Math.round(\n\t\t\t\t\t\t\t\t\t(samples[samples.length - 1].ts - samples[0].ts) / 1000,\n\t\t\t\t\t\t\t\t)}s`\n\t\t\t\t\t\t\t: \"latest turn\"}\n\t\t\t\t\t</div>\n\t\t\t\t</>\n\t\t\t) : (\n\t\t\t\t<div className=\"ov-empty-state\">No cache-hit samples yet</div>\n\t\t\t)}\n\t\t</div>\n\t);\n};\n","/**\n * dashboard-client/src/tabs/CacheTab.tsx -- Cache tab (B.3 + A3).\n *\n * Sections:\n * 1. Provider Prompt Cache -> ProviderCacheCard (/api/provider-cache)\n * 2. Cache Stripe Distribution -> StripeDistributionCard (/api/cache-stripes)\n * 3. Cache Hit-Rate Trend -> CacheHitRateTrendCard (/api/perf)\n * 4. Mega-Compact Dedup Cache -> CacheHitsCard + TimeSavedCard (/api/snapshot)\n *\n * Each section fetches independently; failures in one do not affect others.\n * 5s polling on provider cache and activity snapshot; 15s on stripes + trend.\n */\n\nimport type React from \"react\";\nimport { useCallback } from \"react\";\nimport type {\n\tSnapshotResponse,\n\tProviderCacheResponse,\n\tCacheStripesResponse,\n\tPerfResponse,\n} from \"@contracts\";\nimport { useApi } from \"../hooks/useApi\";\nimport {\n\tfetchSnapshot,\n\tfetchProviderCache,\n\tfetchCacheStripes,\n\tfetchPerf,\n} from \"../api/client\";\nimport { CacheHitsCard } from \"../components/CacheHitsCard\";\nimport { TimeSavedCard } from \"../components/TimeSavedCard\";\nimport { ProviderCacheCard } from \"../components/ProviderCacheCard\";\nimport { StripeDistributionCard } from \"../components/StripeDistributionCard\";\nimport { CacheHitRateTrendCard } from \"../components/CacheHitRateTrendCard\";\n\nexport default function CacheTab(): React.ReactElement {\n\t/* --- Provider prompt cache (5s poll) --- */\n\tconst {\n\t\tdata: providerCache,\n\t\tloading: pcLoading,\n\t\terror: pcError,\n\t} = useApi<ProviderCacheResponse>(\n\t\tuseCallback(() => fetchProviderCache(), []),\n\t\t{ pollInterval: 5000 },\n\t);\n\n\t/* --- Cache stripe distribution (15s poll) --- */\n\tconst {\n\t\tdata: stripes,\n\t\tloading: stripesLoading,\n\t\terror: stripesError,\n\t} = useApi<CacheStripesResponse>(\n\t\tuseCallback(() => fetchCacheStripes(), []),\n\t\t{ pollInterval: 15000 },\n\t);\n\n\t/* --- Cache hit-rate trend from perf samples (15s poll) --- */\n\tconst {\n\t\tdata: perf,\n\t\tloading: perfLoading,\n\t\terror: perfError,\n\t} = useApi<PerfResponse>(\n\t\tuseCallback(() => fetchPerf({ minutes: 30 }), []),\n\t\t{ pollInterval: 15000 },\n\t);\n\n\t/* --- Dedup cache snapshot (5s poll) --- */\n\tconst {\n\t\tdata: snapshot,\n\t\tloading: snapLoading,\n\t\terror: snapError,\n\t} = useApi<SnapshotResponse>(\n\t\tuseCallback(() => fetchSnapshot(), []),\n\t\t{ pollInterval: 5000 },\n\t);\n\n\treturn (\n\t\t<div className=\"cache-tab\">\n\t\t\t{/* ==============================================================\n\t\t\t Section 1 -- Provider Prompt Cache\n\t\t\t ============================================================== */}\n\t\t\t<h2>Provider Prompt Cache</h2>\n\t\t\t{pcLoading ? (\n\t\t\t\t<p className=\"tab-stub\">Loading...</p>\n\t\t\t) : pcError ? (\n\t\t\t\t<p className=\"tab-stub\">\n\t\t\t\t\tProvider cache unavailable: {pcError.message}\n\t\t\t\t</p>\n\t\t\t) : providerCache ? (\n\t\t\t\t<ProviderCacheCard\n\t\t\t\t\thitPct={providerCache.cache.avgHitPct}\n\t\t\t\t\tturnCount={providerCache.cache.turnCount}\n\t\t\t\t\ttotalCacheRead={providerCache.cache.totalCacheRead}\n\t\t\t\t\ttotalCacheWrite={providerCache.cache.totalCacheWrite}\n\t\t\t\t\ttotalInput={providerCache.cache.totalInput}\n\t\t\t\t\tcacheReadSaved={providerCache.savings?.cacheReadSaved ?? null}\n\t\t\t\t\tcacheWriteCost={providerCache.savings?.cacheWriteCost ?? null}\n\t\t\t\t\tnetSaved={providerCache.savings?.netSaved ?? null}\n\t\t\t\t\tmodelLabel={providerCache.savings?.model ?? null}\n\t\t\t\t\tfirstTurnAt={providerCache.cache.firstTurnAt}\n\t\t\t\t\tlatestTurnAt={providerCache.cache.latestTurnAt}\n\t\t\t\t\tbyModel={providerCache.cache.byModel ?? []}\n\t\t\t\t/>\n\t\t\t) : null}\n\n\t\t\t{/* ==============================================================\n\t\t\t Section 2 -- Cache Stripe Distribution\n\t\t\t ============================================================== */}\n\t\t\t<h2>Cache Stripe Distribution</h2>\n\t\t\t{stripesLoading ? (\n\t\t\t\t<p className=\"tab-stub\">Loading...</p>\n\t\t\t) : stripesError ? (\n\t\t\t\t<p className=\"tab-stub\">\n\t\t\t\t\tCache stripe data unavailable: {stripesError.message}\n\t\t\t\t</p>\n\t\t\t) : stripes ? (\n\t\t\t\t<StripeDistributionCard data={stripes} />\n\t\t\t) : null}\n\n\t\t\t{/* ==============================================================\n\t\t\t Section 3 -- Cache Hit-Rate Trend\n\t\t\t ============================================================== */}\n\t\t\t<h2>Cache Hit-Rate Trend</h2>\n\t\t\t{perfLoading ? (\n\t\t\t\t<p className=\"tab-stub\">Loading...</p>\n\t\t\t) : perfError ? (\n\t\t\t\t<p className=\"tab-stub\">\n\t\t\t\t\tHit-rate trend unavailable: {perfError.message}\n\t\t\t\t</p>\n\t\t\t) : perf ? (\n\t\t\t\t<CacheHitRateTrendCard perf={perf} />\n\t\t\t) : null}\n\n\t\t\t{/* ==============================================================\n\t\t\t Section 4 -- Mega-Compact Dedup Cache\n\t\t\t ============================================================== */}\n\t\t\t<h2>Mega-Compact Dedup Cache</h2>\n\t\t\t{snapLoading ? (\n\t\t\t\t<p className=\"tab-stub\">Loading...</p>\n\t\t\t) : snapError ? (\n\t\t\t\t<p className=\"tab-stub\">Snapshot unavailable: {snapError.message}</p>\n\t\t\t) : (\n\t\t\t\tsnapshot &&\n\t\t\t\t(() => {\n\t\t\t\t\tconst { cacheHits, compacts, timeSaved } = snapshot;\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<div className=\"card-grid overview-card-grid\">\n\t\t\t\t\t\t\t<CacheHitsCard\n\t\t\t\t\t\t\t\tcacheHitsSession={cacheHits.session}\n\t\t\t\t\t\t\t\tcacheHitsTotal={cacheHits.total}\n\t\t\t\t\t\t\t\ttokensSavedSession={cacheHits.sessionTokensSaved}\n\t\t\t\t\t\t\t\ttokensSavedTotal={cacheHits.totalTokensSaved}\n\t\t\t\t\t\t\t\tcompactionsSession={compacts.session}\n\t\t\t\t\t\t\t\tcompactionsTotal={compacts.total}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<TimeSavedCard\n\t\t\t\t\t\t\t\tcompactSessionSec={timeSaved.compact.sessionSec}\n\t\t\t\t\t\t\t\tcompactTotalSec={timeSaved.compact.totalSec}\n\t\t\t\t\t\t\t\tcacheHitSessionSec={timeSaved.cacheHit.sessionSec}\n\t\t\t\t\t\t\t\tcacheHitTotalSec={timeSaved.cacheHit.totalSec}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t);\n\t\t\t\t})()\n\t\t\t)}\n\t\t</div>\n\t);\n}\n"],"names":["hitPctClass","pct","netClass","v","StatRow","label","value","className","title","jsxs","jsx","ProviderCacheCard","props","fmtTokens","fmtDollars","fmtDate","fmtRelativeTime","Fragment","m","rate","lookupModelInputRate","cr","cw","STRIPE_COLORS","STRIPE_LABELS","HEALTH_LABEL_COLOR","StripeDistributionCard","data","maxCount","b","healthColor","fmtPctFromFraction","bucket","colorClass","fmtPct","n","pctColorClass","CacheHitRateTrendCard","perf","c","samples","i","barH","isLatest","CacheTab","providerCache","pcLoading","pcError","useApi","useCallback","fetchProviderCache","stripes","stripesLoading","stripesError","fetchCacheStripes","perfLoading","perfError","fetchPerf","snapshot","snapLoading","snapError","fetchSnapshot","cacheHits","compacts","timeSaved","CacheHitsCard","TimeSavedCard"],"mappings":"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"}
|