pi-mega-compact 0.13.2 → 0.13.3
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/endpoints/registry.js +13 -0
- package/dist/extensions/dashboard-server/api-contracts/rag-settings.js +10 -0
- package/dist/extensions/dashboard-server/routes-rag-settings.js +169 -0
- package/dist/extensions/dashboard-server/routes.js +1 -0
- package/dist/extensions/dashboard-server/server.js +3 -1
- package/dist/src/config.js +14 -7
- package/dist/src/recall/sync.js +4 -3
- package/extensions/dashboard-client/dist/assets/{CacheTab-DPRHjYZ0.js → CacheTab-Cqnc7lMg.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-DPRHjYZ0.js.map → CacheTab-Cqnc7lMg.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-CgwYq-Qf.js → EventsTab-tYk3VFAp.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-CgwYq-Qf.js.map → EventsTab-tYk3VFAp.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-BgOCjVzR.js → HealthTab-D2_kjgxw.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-BgOCjVzR.js.map → HealthTab-D2_kjgxw.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-Yw1yFpsy.js → MaintenanceTab-Bc-uPYrz.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-Yw1yFpsy.js.map → MaintenanceTab-Bc-uPYrz.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-yHUX7Yzi.js → MemoryMapTab-D1V9g_NC.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-yHUX7Yzi.js.map → MemoryMapTab-D1V9g_NC.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CcH9fP8P.js → MetricsTab-DT_XfL96.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CcH9fP8P.js.map → MetricsTab-DT_XfL96.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-BOs8ZNev.js → OverviewTab-BY6KZCme.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-BOs8ZNev.js.map → OverviewTab-BY6KZCme.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-B-MfSPvg.js → ReposTab-CeS-tpDM.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-B-MfSPvg.js.map → ReposTab-CeS-tpDM.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-BmeN649Q.js → SessionsTab-C30AwyoF.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-BmeN649Q.js.map → SessionsTab-C30AwyoF.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/SetupTab-BJUHOVK2.js +2 -0
- package/extensions/dashboard-client/dist/assets/SetupTab-BJUHOVK2.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DnPg0L0z.js → TimeSavedCard-p55K1tFP.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DnPg0L0z.js.map → TimeSavedCard-p55K1tFP.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TopicsTab-BODHOg_b.js → TopicsTab-D8FqyAbK.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TopicsTab-BODHOg_b.js.map → TopicsTab-D8FqyAbK.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-BK0I7hKr.js → TurnsTab-ju-nXDys.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-BK0I7hKr.js.map → TurnsTab-ju-nXDys.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-mOlljTFI.js → index-BEj1Zahy.js} +8 -8
- package/extensions/dashboard-client/dist/assets/index-BEj1Zahy.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{useSSE-BYOtv1-3.js → useSSE-p1pOI_-P.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-BYOtv1-3.js.map → useSSE-p1pOI_-P.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/api/client.ts +17 -0
- package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderSetup.tsx +2 -0
- package/extensions/dashboard-client/src/tabs/SetupTab/RagSettingsCard.tsx +160 -0
- package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +23 -0
- package/extensions/dashboard-server/api-contracts/index.ts +8 -0
- package/extensions/dashboard-server/api-contracts/rag-settings.ts +42 -0
- package/extensions/dashboard-server/routes-rag-settings.ts +207 -0
- package/extensions/dashboard-server/routes.ts +1 -0
- package/extensions/dashboard-server/server.ts +2 -0
- package/package.json +1 -1
- package/src/config.ts +15 -7
- package/src/recall/sync.ts +4 -3
- package/extensions/dashboard-client/dist/assets/SetupTab-nOknoph5.js +0 -2
- package/extensions/dashboard-client/dist/assets/SetupTab-nOknoph5.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/index-mOlljTFI.js.map +0 -1
|
@@ -222,4 +222,17 @@ export const ENDPOINTS = {
|
|
|
222
222
|
path: "/api/embedder-health",
|
|
223
223
|
description: "Probe the active embedder with a test embed and report status, latency, dimensions, and masked URL.",
|
|
224
224
|
},
|
|
225
|
+
// ─── RAG Settings (Sprint B6) ─────────────────────────────────────
|
|
226
|
+
/** GET /api/rag-settings — Read the state of all RAG feature flags. */
|
|
227
|
+
ragSettings: {
|
|
228
|
+
method: "GET",
|
|
229
|
+
path: "/api/rag-settings",
|
|
230
|
+
description: "Read the current state of all RAG feature flags (enabled/disabled).",
|
|
231
|
+
},
|
|
232
|
+
/** POST /api/rag-settings — Update RAG feature flag states. */
|
|
233
|
+
ragSettingsUpdate: {
|
|
234
|
+
method: "POST",
|
|
235
|
+
path: "/api/rag-settings",
|
|
236
|
+
description: "Update RAG feature flag states by writing _DISABLED env vars to .mega-compact.env.",
|
|
237
|
+
},
|
|
225
238
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* api-contracts/rag-settings.ts — RAG feature flag API response types.
|
|
3
|
+
*
|
|
4
|
+
* Types for the /api/rag-settings endpoint used by the dashboard RAG Settings
|
|
5
|
+
* panel to read and toggle the five RAG feature flags (B1–B5).
|
|
6
|
+
*
|
|
7
|
+
* PREVENT-PI-004: type definitions only, no network code.
|
|
8
|
+
* PREVENT-011: no `any` type — all types are explicit.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-server/routes-rag-settings.ts — RAG Settings route handler.
|
|
3
|
+
*
|
|
4
|
+
* GET /api/rag-settings — Returns the state of all RAG feature flags (B1–B5).
|
|
5
|
+
* POST /api/rag-settings — Toggles flags by writing MEGACOMPACT_*_DISABLED
|
|
6
|
+
* lines to the per-repo .mega-compact.env file.
|
|
7
|
+
*
|
|
8
|
+
* Guardrails: PREVENT-PI-004 (loopback-only), PREVENT-001 (null-safe JSON),
|
|
9
|
+
* PREVENT-011 (no `any`). Each JSON write carries a guardrails-allow annotation.
|
|
10
|
+
*/
|
|
11
|
+
import { readFileSync, existsSync, writeFileSync, mkdirSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { detectCurrentEmbedder } from "./routes-setup.js";
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// RAG_FLAGS — the five feature flags surfaced by this panel.
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
const RAG_FLAGS = [
|
|
18
|
+
{
|
|
19
|
+
key: "MEGACOMPACT_QUERY_REFORMULATION",
|
|
20
|
+
label: "Query Reformulation",
|
|
21
|
+
description: "TF-IDF keyword expansion for vague recall queries (TrigramEmbedder path)",
|
|
22
|
+
requiresLlm: false,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
key: "MEGACOMPACT_TIERED_ROUTER",
|
|
26
|
+
label: "Tiered Recall Router",
|
|
27
|
+
description: "L0 cache → L1 FTS5 → L2 HNSW routing for faster recall",
|
|
28
|
+
requiresLlm: false,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
key: "MEGACOMPACT_RECALL_METRICS",
|
|
32
|
+
label: "Recall Quality Metrics",
|
|
33
|
+
description: "Precision/recall scoring and logging for recall evaluation",
|
|
34
|
+
requiresLlm: false,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
key: "MEGACOMPACT_MEMORY_GRAPH",
|
|
38
|
+
label: "Memory Graph",
|
|
39
|
+
description: "Dashboard-oriented memory graph traversal across sessions",
|
|
40
|
+
requiresLlm: false,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
key: "MEGACOMPACT_HYDE",
|
|
44
|
+
label: "HyDE (Hypothetical Document Embeddings)",
|
|
45
|
+
description: "Generate hypothetical answer via LLM, embed it, RRF-fuse with raw-query results",
|
|
46
|
+
requiresLlm: true,
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
/** Strip any MEGACOMPACT_*_DISABLED assignment lines from env file content. */
|
|
50
|
+
const DISABLED_LINE = /^export\s+MEGACOMPACT_\w+_DISABLED=/;
|
|
51
|
+
function isDisabled(key) {
|
|
52
|
+
const v = process.env[key + "_DISABLED"];
|
|
53
|
+
return v === "true" || v === "1";
|
|
54
|
+
}
|
|
55
|
+
function readJsonBody(req, cb) {
|
|
56
|
+
let body = "";
|
|
57
|
+
let tooBig = false;
|
|
58
|
+
req.on("data", (chunk) => {
|
|
59
|
+
if (body.length > 65536) {
|
|
60
|
+
tooBig = true;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
body += chunk.toString();
|
|
64
|
+
});
|
|
65
|
+
req.on("end", () => {
|
|
66
|
+
if (tooBig)
|
|
67
|
+
return cb({ ok: false, error: "body_too_large" });
|
|
68
|
+
try {
|
|
69
|
+
const v = body ? JSON.parse(body) : {}; // PREVENT-001: parsed value type-checked below
|
|
70
|
+
if (typeof v !== "object" || v === null || Array.isArray(v)) {
|
|
71
|
+
return cb({ ok: false, error: "invalid_object" });
|
|
72
|
+
}
|
|
73
|
+
cb({ ok: true, value: v });
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
cb({ ok: false, error: "invalid_json" });
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
req.on("error", () => cb({ ok: false, error: "read_error" }));
|
|
80
|
+
}
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// handleRagSettings — "/api/rag-settings"
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
export function handleRagSettings(req, res, ctx) {
|
|
85
|
+
if (req.url !== "/api/rag-settings")
|
|
86
|
+
return false;
|
|
87
|
+
if (req.method === "GET") {
|
|
88
|
+
const flags = RAG_FLAGS.map((f) => ({
|
|
89
|
+
key: f.key,
|
|
90
|
+
label: f.label,
|
|
91
|
+
description: f.description,
|
|
92
|
+
enabled: !isDisabled(f.key),
|
|
93
|
+
requiresLlm: f.requiresLlm,
|
|
94
|
+
}));
|
|
95
|
+
const body = {
|
|
96
|
+
flags,
|
|
97
|
+
llmActive: detectCurrentEmbedder() === "http",
|
|
98
|
+
};
|
|
99
|
+
// guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
|
|
100
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
101
|
+
res.end(JSON.stringify(body));
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
if (req.method === "POST") {
|
|
105
|
+
readJsonBody(req, (parsed) => {
|
|
106
|
+
if (!parsed.ok) {
|
|
107
|
+
// guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
|
|
108
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
109
|
+
res.end(JSON.stringify({ error: parsed.error }));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const body = parsed.value;
|
|
113
|
+
const desired = body.flags;
|
|
114
|
+
if (typeof desired !== "object" || desired === null || Array.isArray(desired)) {
|
|
115
|
+
// guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
|
|
116
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
117
|
+
res.end(JSON.stringify({ error: "invalid_flags" }));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// Only accept known RAG flag keys.
|
|
121
|
+
const known = new Set(RAG_FLAGS.map((f) => f.key));
|
|
122
|
+
for (const key of Object.keys(desired)) {
|
|
123
|
+
if (!known.has(key)) {
|
|
124
|
+
// guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
|
|
125
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
126
|
+
res.end(JSON.stringify({ error: `unknown_flag: ${key}` }));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const envPath = join(ctx.stateDir, ".mega-compact.env");
|
|
131
|
+
let lines = [];
|
|
132
|
+
if (existsSync(envPath)) {
|
|
133
|
+
const content = readFileSync(envPath, "utf-8");
|
|
134
|
+
lines = content.split("\n").filter((line) => !DISABLED_LINE.test(line));
|
|
135
|
+
}
|
|
136
|
+
for (const key of Object.keys(desired)) {
|
|
137
|
+
if (desired[key] === false) {
|
|
138
|
+
lines.push(`export ${key}_DISABLED="true"`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (lines.length > 0 && lines[lines.length - 1] !== "")
|
|
142
|
+
lines.push("");
|
|
143
|
+
try {
|
|
144
|
+
mkdirSync(ctx.stateDir, { recursive: true });
|
|
145
|
+
writeFileSync(envPath, lines.join("\n"), "utf-8");
|
|
146
|
+
}
|
|
147
|
+
catch (e) {
|
|
148
|
+
// guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
|
|
149
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
150
|
+
res.end(JSON.stringify({
|
|
151
|
+
error: `write_failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
152
|
+
}));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const resp = {
|
|
156
|
+
envPath,
|
|
157
|
+
restartRequired: true,
|
|
158
|
+
};
|
|
159
|
+
// guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
|
|
160
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
161
|
+
res.end(JSON.stringify(resp));
|
|
162
|
+
});
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
// guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
|
|
166
|
+
res.writeHead(405, { "Content-Type": "application/json" });
|
|
167
|
+
res.end(JSON.stringify({ error: "method_not_allowed" }));
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
@@ -19,3 +19,4 @@ export { handleRaptorTree } from "./routes-raptor.js";
|
|
|
19
19
|
export { handleCacheStripes } from "./routes-cache.js";
|
|
20
20
|
export { handleContextHealth, handleCachePoison, handleHealthSettings } from "./routes-health.js";
|
|
21
21
|
export { handleEmbedderHealth } from "./routes-embedder-health.js";
|
|
22
|
+
export { handleRagSettings } from "./routes-rag-settings.js";
|
|
@@ -12,7 +12,7 @@ import { join, dirname } from "node:path";
|
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
13
|
import { createRequire } from "node:module";
|
|
14
14
|
import { log, setLogPath, setDashboardServerVersion } from "./state.js";
|
|
15
|
-
import { buildRouteContext, handleIndex, handleRepoIndex, handleEvents, handleGameState, handleGameScores, handlePerf, handleAchievements, handleSessions, handleTopics, handleTurns, handleMaintenance, handleProviderCache, handleCacheStripes, handleMemoryStatus, handleSetupStatus, handleSetupDetect, handleSetupConfigure, handleMemoryMap, handleRaptorTree, handleContextHealth, handleCachePoison, handleHealthSettings, handleEmbedderHealth, handleStatic, } from "./routes.js";
|
|
15
|
+
import { buildRouteContext, handleIndex, handleRepoIndex, handleEvents, handleGameState, handleGameScores, handlePerf, handleAchievements, handleSessions, handleTopics, handleTurns, handleMaintenance, handleProviderCache, handleCacheStripes, handleMemoryStatus, handleSetupStatus, handleSetupDetect, handleSetupConfigure, handleMemoryMap, handleRaptorTree, handleContextHealth, handleCachePoison, handleHealthSettings, handleEmbedderHealth, handleRagSettings, handleStatic, } from "./routes.js";
|
|
16
16
|
export async function launchDashboardServer(stateDir) {
|
|
17
17
|
// Our own package version — exposed at /api/version so the launcher can
|
|
18
18
|
// detect a stale server (started by an older build) and replace it on
|
|
@@ -217,6 +217,8 @@ export async function launchDashboardServer(stateDir) {
|
|
|
217
217
|
return;
|
|
218
218
|
if (handleEmbedderHealth(req, res, ctx))
|
|
219
219
|
return;
|
|
220
|
+
if (handleRagSettings(req, res, ctx))
|
|
221
|
+
return;
|
|
220
222
|
handleStatic(req, res, ctx);
|
|
221
223
|
});
|
|
222
224
|
// Bind base + range are env-configurable so tests can use a private,
|
package/dist/src/config.js
CHANGED
|
@@ -82,7 +82,9 @@ export function memoryReviewCadence(band, baseInterval) {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
// ---------------------------------------------------------------------------
|
|
85
|
-
// S57 RAG Suite feature flags —
|
|
85
|
+
// S57 RAG Suite feature flags — all default ON with graceful fallback; opt OUT
|
|
86
|
+
// via MEGACOMPACT_<NAME>_DISABLED=true. Every feature degrades to existing
|
|
87
|
+
// behavior on error (non-fatal, best-effort).
|
|
86
88
|
// ---------------------------------------------------------------------------
|
|
87
89
|
function ragFlag(name) {
|
|
88
90
|
const v = process.env[name];
|
|
@@ -90,13 +92,18 @@ function ragFlag(name) {
|
|
|
90
92
|
return false;
|
|
91
93
|
return v === "true" || v === "1";
|
|
92
94
|
}
|
|
95
|
+
function ragEnabled(name) {
|
|
96
|
+
return !ragFlag(name + "_DISABLED");
|
|
97
|
+
}
|
|
93
98
|
/** B1: Query reformulation (keyword expansion via embedding neighbors). */
|
|
94
|
-
export const RAG_QUERY_REFORMULATION = () =>
|
|
99
|
+
export const RAG_QUERY_REFORMULATION = () => ragEnabled("MEGACOMPACT_QUERY_REFORMULATION");
|
|
95
100
|
/** B2: Tiered recall router (L0 cache → L1 FTS5 → L2 HNSW). */
|
|
96
|
-
export const RAG_TIERED_ROUTER = () =>
|
|
101
|
+
export const RAG_TIERED_ROUTER = () => ragEnabled("MEGACOMPACT_TIERED_ROUTER");
|
|
97
102
|
/** B3: Recall quality metrics (precision/recall scoring + logging). */
|
|
98
|
-
export const RAG_RECALL_METRICS = () =>
|
|
103
|
+
export const RAG_RECALL_METRICS = () => ragEnabled("MEGACOMPACT_RECALL_METRICS");
|
|
99
104
|
/** B4: Memory graph traversal (dashboard-oriented). */
|
|
100
|
-
export const RAG_MEMORY_GRAPH = () =>
|
|
101
|
-
/** B5: HyDE — generate a hypothetical answer doc via LLM
|
|
102
|
-
|
|
105
|
+
export const RAG_MEMORY_GRAPH = () => ragEnabled("MEGACOMPACT_MEMORY_GRAPH");
|
|
106
|
+
/** B5: HyDE — generate a hypothetical answer doc via LLM. Auto-ON when an
|
|
107
|
+
* HttpEmbedder is active (the LLM is configured for indexing); opt OUT with
|
|
108
|
+
* MEGACOMPACT_HYDE_DISABLED=true. TrigramEmbedder path is unaffected. */
|
|
109
|
+
export const RAG_HYDE_ENABLED = () => ragEnabled("MEGACOMPACT_HYDE");
|
package/dist/src/recall/sync.js
CHANGED
|
@@ -78,9 +78,10 @@ export function recallAndInline(opts, store) {
|
|
|
78
78
|
// F1: hoist one embedder instance (shared by HyDE below + inline dedupe later).
|
|
79
79
|
// defaultEmbedder() is deterministic but creating it per call wastes allocations.
|
|
80
80
|
const embedder = defaultEmbedder();
|
|
81
|
-
// S43 re-plan: HyDE — when HttpEmbedder is active
|
|
82
|
-
//
|
|
83
|
-
//
|
|
81
|
+
// S43 re-plan: HyDE — auto-ON when an HttpEmbedder is active (LLM configured
|
|
82
|
+
// for indexing); opt out via MEGACOMPACT_HYDE_DISABLED=true. Generates a
|
|
83
|
+
// hypothetical answer doc, embeds it, runs a second searchRecall, and
|
|
84
|
+
// RRF-fuses with the raw-query results. Non-fatal: any error → raw-only.
|
|
84
85
|
// TrigramEmbedder (kind !== "http") skips this — S57 B1 reformulation above
|
|
85
86
|
// already handles the no-LLM path.
|
|
86
87
|
let hydeHits = null;
|
package/extensions/dashboard-client/dist/assets/{CacheTab-DPRHjYZ0.js → CacheTab-Cqnc7lMg.js}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{j as e,r as h,u as j,
|
|
2
|
-
//# sourceMappingURL=CacheTab-
|
|
1
|
+
import{j as e,r as h,u as j,q as R,s as P,o as E,g as $}from"./index-BEj1Zahy.js";import{C as L,T as A}from"./TimeSavedCard-p55K1tFP.js";import{b as v,c as u,d as H,e as M,f as o}from"./format-COjDIVGj.js";import{l as I}from"./pricing-CvE4NllQ.js";function N(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:s,className:t,title:l}){return e.jsxs("div",{className:"ov-stat-row",children:[e.jsx("span",{className:"ov-stat-label",title:l,children:a}),e.jsx("span",{className:`ov-stat-value${t?` ${t}`:""}`,children:s})]})}function w(a){return e.jsxs("div",{className:"card provider-cache-card",children:[e.jsx(r,{label:"Cache Hit Rate",value:`${a.hitPct.toFixed(1)}%`,className:N(a.hitPct)}),e.jsx(r,{label:"Turns Recorded",value:String(a.turnCount)}),e.jsx(r,{label:"Cache Read Tokens",value:v(a.totalCacheRead)}),e.jsx(r,{label:"Cache Write Tokens",value:v(a.totalCacheWrite)}),e.jsx(r,{label:"Input Tokens",value:v(a.totalInput)}),e.jsx(r,{label:"$ Saved (reads)",value:u(a.cacheReadSaved)}),e.jsx(r,{label:"Write Investment",value:u(a.cacheWriteCost)}),e.jsx(r,{label:"Net Saved",value:u(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(s=>{const t=I(s.model),l=t!=null?s.totalCacheRead/1e6*t:null,n=t!=null?s.totalCacheWrite/1e6*t:null;return e.jsxs("div",{className:"ov-stat-group",children:[e.jsx(r,{label:"Model",value:s.model}),e.jsx(r,{label:"Hit Rate",value:`${s.hitPct.toFixed(1)}%`,className:N(s.hitPct)}),e.jsx(r,{label:"Cache Read",value:v(s.totalCacheRead)}),e.jsx(r,{label:"Cache Write",value:v(s.totalCacheWrite)}),e.jsx(r,{label:"Samples",value:String(s.sampleCount)}),e.jsx(r,{label:"$ Saved (reads)",value:l!=null?u(l):"—"}),e.jsx(r,{label:"Write Investment",value:n!=null?u(n):"—"})]},s.model)})]})]})}const W=["ov-bar-green","ov-bar-blue","ov-bar-teal","ov-bar-yellow","ov-bar-gray"],D=["Permanent","Epoch","Topic","Thread","Volatile"],_={good:"ov-text-green",fair:"ov-text-yellow",degraded:"ov-text-orange",poor:"ov-text-red"};function F({data:a}){const s=Math.max(...a.buckets.map(l=>l.count),1),t=_[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 ${t}`,children:a.health.label}),e.jsx("span",{className:"ov-stripe-health-score",children:o(a.health.score)}),e.jsxs("span",{className:"ov-stripe-churn",children:["Churn: ",o(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(l=>{const n=s>0?Math.max(0,Math.min(100,l.count/s*100)):0,c=W[l.stripe]??"ov-bar-gray",i=l.label||(D[l.stripe]??`Stripe ${l.stripe}`);return e.jsxs("div",{className:"ov-stripe-row",children:[e.jsx("span",{className:"ov-stripe-label",title:i,children:i}),e.jsx("div",{className:"ov-bar-track",children:e.jsx("div",{className:`ov-bar-fill ${c}`,style:{width:`${n}%`}})}),e.jsx("span",{className:"ov-stripe-count",children:l.count}),e.jsx("span",{className:"ov-stripe-stability",title:`min ${o(l.minStability)} / max ${o(l.maxStability)}`,children:o(l.avgStability)})]},l.stripe)})}),e.jsxs("div",{className:"ov-stripe-footer",children:["Epoch: ",a.epochId??"—","· Dominant tier: ",o(a.health.dominantTier)]})]})}function m(a){return`${a.toFixed(1)}%`}function O(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 G=({perf:a})=>{const{cache_hit_pct:s}=a,t=s.samples&&s.samples.length>0?s.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:m(s.avg)})]}),e.jsxs("div",{className:"ov-perf-stat",children:["Latest: ",e.jsx("strong",{children:m(s.latest)})]}),e.jsxs("div",{className:"ov-perf-stat",children:["Samples: ",e.jsx("strong",{children:s.n})]})]}),t.length>0?e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"ov-sparkline",role:"img","aria-label":`Cache hit rate trend: ${s.n} samples, average ${m(s.avg)}, latest ${m(s.latest)}`,children:t.map((l,n)=>{const c=Math.max(2,Math.round(l.pct*.6)),i=O(l.pct),x=n===t.length-1;return e.jsx("div",{className:`ov-sparkline-bar ${i}${x?" ov-bar-latest":""}`,style:{height:`${c}px`},title:`${m(l.pct)} at ${new Date(l.ts).toLocaleTimeString()}`},n)})}),e.jsxs("div",{className:"ov-card-footer",children:[t.length," sample",t.length!==1?"s":""," over"," ",t.length>1?`${Math.round((t[t.length-1].ts-t[0].ts)/1e3)}s`:"latest turn"]})]}):e.jsx("div",{className:"ov-empty-state",children:"No cache-hit samples yet"})]})};function V(){const[a,s]=h.useState(!1),{data:t,loading:l,error:n}=j(h.useCallback(()=>R(),[]),{pollInterval:5e3}),{data:c,loading:i,error:x}=j(h.useCallback(()=>P(),[]),{pollInterval:15e3}),{data:g,loading:T,error:C}=j(h.useCallback(()=>E({minutes:30}),[]),{pollInterval:15e3}),{data:d,loading:y,error:S}=j(h.useCallback(()=>$(),[]),{pollInterval:5e3});return e.jsxs("div",{className:"cache-tab",children:[e.jsxs("div",{className:"card-section",children:[e.jsx("button",{type:"button",className:"info-toggle",onClick:()=>s(!a),children:a?"Hide Guidance":"Show Cache-Friendly Prompt Ordering Guidance"}),a&&e.jsxs("div",{className:"info-panel",children:[e.jsx("h3",{children:"Cache-Friendly Prompt Ordering"}),e.jsxs("ul",{children:[e.jsx("li",{children:"Keep system prompts and stable context at the top of your conversation — the provider caches the leading prefix."}),e.jsxs("li",{children:["Tool results and volatile content are automatically moved to the tail by message separation (",e.jsx("code",{children:"MEGACOMPACT_MESSAGE_SEPARATION"}),") so they don't invalidate the cache prefix."]}),e.jsxs("li",{children:["Cache striping (",e.jsx("code",{children:"MEGACOMPACT_CACHE_STRIPING"}),") further orders stable context by a stability score so the most durable chunks lead."]}),e.jsx("li",{children:"Avoid inserting new instructions mid-conversation — prepend them instead."})]}),d&&e.jsxs("div",{className:"cache-status-indicator",children:[e.jsx("strong",{children:"Status:"})," ",d.config.messageSeparation?"Separation Enabled":"Separation Disabled (Check env)"," |"," ",d.config.cacheStriping?"Striping Enabled":"Striping Disabled (Check env)"]})]})]}),e.jsx("h2",{children:"Provider Prompt Cache"}),l?e.jsx("p",{className:"tab-stub",children:"Loading..."}):n?e.jsxs("p",{className:"tab-stub",children:["Provider cache unavailable: ",n.message]}):t?e.jsx(w,{hitPct:t.cache.avgHitPct,turnCount:t.cache.turnCount,totalCacheRead:t.cache.totalCacheRead,totalCacheWrite:t.cache.totalCacheWrite,totalInput:t.cache.totalInput,cacheReadSaved:t.savings?.cacheReadSaved??null,cacheWriteCost:t.savings?.cacheWriteCost??null,netSaved:t.savings?.netSaved??null,modelLabel:t.savings?.model??null,firstTurnAt:t.cache.firstTurnAt,latestTurnAt:t.cache.latestTurnAt,byModel:t.cache.byModel??[]}):null,e.jsx("h2",{children:"Cache Stripe Distribution"}),i?e.jsx("p",{className:"tab-stub",children:"Loading..."}):x?e.jsxs("p",{className:"tab-stub",children:["Cache stripe data unavailable: ",x.message]}):c?e.jsx(F,{data:c}):null,e.jsx("h2",{children:"Cache Hit-Rate Trend"}),T?e.jsx("p",{className:"tab-stub",children:"Loading..."}):C?e.jsxs("p",{className:"tab-stub",children:["Hit-rate trend unavailable: ",C.message]}):g?e.jsx(G,{perf:g}):null,e.jsx("h2",{children:"Mega-Compact Dedup Cache"}),y?e.jsx("p",{className:"tab-stub",children:"Loading..."}):S?e.jsxs("p",{className:"tab-stub",children:["Snapshot unavailable: ",S.message]}):d&&(()=>{const{cacheHits:p,compacts:f,timeSaved:b}=d;return e.jsxs("div",{className:"card-grid overview-card-grid",children:[e.jsx(L,{cacheHitsSession:p.session,cacheHitsTotal:p.total,tokensSavedSession:p.sessionTokensSaved,tokensSavedTotal:p.totalTokensSaved,compactionsSession:f.session,compactionsTotal:f.total}),e.jsx(A,{compactSessionSec:b.compact.sessionSec,compactTotalSec:b.compact.totalSec,cacheHitSessionSec:b.cacheHit.sessionSec,cacheHitTotalSec:b.cacheHit.totalSec})]})})()]})}export{V as default};
|
|
2
|
+
//# sourceMappingURL=CacheTab-Cqnc7lMg.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CacheTab-DPRHjYZ0.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 { useState, 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\tconst [infoExpanded, setInfoExpanded] = useState(false);\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<div className=\"card-section\">\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclassName=\"info-toggle\"\n\t\t\t\t\tonClick={() => setInfoExpanded(!infoExpanded)}\n\t\t\t\t>\n\t\t\t\t\t{infoExpanded ? \"Hide Guidance\" : \"Show Cache-Friendly Prompt Ordering Guidance\"}\n\t\t\t\t</button>\n\t\t\t\t{infoExpanded && (\n\t\t\t\t\t<div className=\"info-panel\">\n\t\t\t\t\t\t<h3>Cache-Friendly Prompt Ordering</h3>\n\t\t\t\t\t\t<ul>\n\t\t\t\t\t\t\t<li>Keep system prompts and stable context at the top of your conversation — the provider caches the leading prefix.</li>\n\t\t\t\t\t\t\t<li>Tool results and volatile content are automatically moved to the tail by message separation (<code>MEGACOMPACT_MESSAGE_SEPARATION</code>) so they don't invalidate the cache prefix.</li>\n\t\t\t\t\t\t\t<li>Cache striping (<code>MEGACOMPACT_CACHE_STRIPING</code>) further orders stable context by a stability score so the most durable chunks lead.</li>\n\t\t\t\t\t\t\t<li>Avoid inserting new instructions mid-conversation — prepend them instead.</li>\n\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t{snapshot && (\n\t\t\t\t\t\t\t<div className=\"cache-status-indicator\">\n\t\t\t\t\t\t\t\t<strong>Status:</strong>{\" \"}\n\t\t\t\t\t\t\t\t{snapshot.config.messageSeparation ? \"Separation Enabled\" : \"Separation Disabled (Check env)\"} |{\" \"}\n\t\t\t\t\t\t\t\t{snapshot.config.cacheStriping ? \"Striping Enabled\" : \"Striping Disabled (Check env)\"}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</div>\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","s","i","barH","isLatest","CacheTab","infoExpanded","setInfoExpanded","useState","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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"}
|
|
1
|
+
{"version":3,"file":"CacheTab-Cqnc7lMg.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 { useState, 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\tconst [infoExpanded, setInfoExpanded] = useState(false);\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<div className=\"card-section\">\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclassName=\"info-toggle\"\n\t\t\t\t\tonClick={() => setInfoExpanded(!infoExpanded)}\n\t\t\t\t>\n\t\t\t\t\t{infoExpanded ? \"Hide Guidance\" : \"Show Cache-Friendly Prompt Ordering Guidance\"}\n\t\t\t\t</button>\n\t\t\t\t{infoExpanded && (\n\t\t\t\t\t<div className=\"info-panel\">\n\t\t\t\t\t\t<h3>Cache-Friendly Prompt Ordering</h3>\n\t\t\t\t\t\t<ul>\n\t\t\t\t\t\t\t<li>Keep system prompts and stable context at the top of your conversation — the provider caches the leading prefix.</li>\n\t\t\t\t\t\t\t<li>Tool results and volatile content are automatically moved to the tail by message separation (<code>MEGACOMPACT_MESSAGE_SEPARATION</code>) so they don't invalidate the cache prefix.</li>\n\t\t\t\t\t\t\t<li>Cache striping (<code>MEGACOMPACT_CACHE_STRIPING</code>) further orders stable context by a stability score so the most durable chunks lead.</li>\n\t\t\t\t\t\t\t<li>Avoid inserting new instructions mid-conversation — prepend them instead.</li>\n\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t{snapshot && (\n\t\t\t\t\t\t\t<div className=\"cache-status-indicator\">\n\t\t\t\t\t\t\t\t<strong>Status:</strong>{\" \"}\n\t\t\t\t\t\t\t\t{snapshot.config.messageSeparation ? \"Separation Enabled\" : \"Separation Disabled (Check env)\"} |{\" \"}\n\t\t\t\t\t\t\t\t{snapshot.config.cacheStriping ? \"Striping Enabled\" : \"Striping Disabled (Check env)\"}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</div>\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","s","i","barH","isLatest","CacheTab","infoExpanded","setInfoExpanded","useState","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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"}
|
package/extensions/dashboard-client/dist/assets/{EventsTab-CgwYq-Qf.js → EventsTab-tYk3VFAp.js}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as o,j as t}from"./index-
|
|
2
|
-
//# sourceMappingURL=EventsTab-
|
|
1
|
+
import{r as o,j as t}from"./index-BEj1Zahy.js";import{u}from"./useSSE-p1pOI_-P.js";const _={compact_start:"ev-compact",compact_end:"ev-compact",compact_trigger:"ev-trigger",compact_skip:"ev-trigger",tier_changed:"ev-tier",model_changed:"ev-tier",pressure_lifted:"ev-tier",checkpoint_persisted:"ev-checkpoint",recall_inject:"ev-recall",anchors_updated:"ev-recall",config_updated:"ev-config",config_preset:"ev-config",crew_presence_changed:"ev-crew",crew_turn_changed:"ev-crew",crew_bandit_chosen:"ev-crew",session_sample:"ev-recall",game_ritual_start:"ev-game",game_ritual_stage:"ev-game",game_ritual_end:"ev-game",game_mode_changed:"ev-game",game_render:"ev-game"},m=["all","compact_start","compact_end","recall_inject","checkpoint_persisted"];function p(e){switch(e.type){case"compact_start":return`trigger=${e.trigger} session=${e.sessionId.slice(0,8)}`;case"compact_end":return`freed=${e.tokensFreed} ok=${e.success} cp=${e.checkpointId.slice(0,8)}`;case"compact_trigger":return`pressure=${e.pressure}% threshold=${e.threshold}% armed=${e.armed}`;case"compact_skip":return`reason=${e.reason}`;case"tier_changed":return`${e.from} → ${e.to} ctx=${e.contextPct}%`;case"model_changed":return`${e.providerName}/${e.model}`;case"pressure_lifted":return`${e.beforePct}% → ${e.afterPct}%`;case"checkpoint_persisted":return`cp=${e.checkpointId.slice(0,8)} sessionTok=${e.sessionTokens}`;case"recall_inject":return`q="${e.query.slice(0,40)}" chunks=${e.chunks} tok=${e.tokens}`;case"anchors_updated":return`count=${e.count} pinned=${e.pinned}`;case"config_updated":return`key=${e.key}`;case"config_preset":return`preset=${e.preset}`;case"crew_presence_changed":return`agents=${e.activeAgents} turn=${e.currentTurn}`;case"crew_turn_changed":return`turn=${e.turnIndex} agent=${e.agentName}`;case"crew_bandit_chosen":return`agent=${e.chosenAgent} score=${e.score} regret=${e.regret}`;case"session_sample":return`sess=${e.sessionId.slice(0,8)} tok=${e.tokens.toLocaleString()} ctx=${Math.round(e.percent)}%`;case"game_ritual_start":case"game_ritual_stage":case"game_ritual_end":case"game_mode_changed":case"game_render":return`stage=${e.type}`}}function g(e){try{return new Date(e).toLocaleTimeString()}catch{return e}}function h({events:e}){const[s,r]=o.useState("all"),[a,l]=o.useState(null),i=o.useMemo(()=>(s==="all"?e:e.filter(n=>n.type===s)).slice(-200).reverse(),[e,s]);return t.jsxs("div",{className:"event-stream",children:[t.jsxs("div",{className:"event-filters",role:"toolbar",children:[m.map(c=>t.jsx("button",{type:"button",className:`filter-chip ${s===c?"active":""}`,onClick:()=>r(c),children:c==="all"?"all":c.replace(/_/g," ")},c)),t.jsxs("span",{className:"event-count",children:[e.length," buffered"]})]}),t.jsxs("ul",{className:"event-list",children:[i.length===0&&t.jsx("li",{className:"event-empty",children:"No events yet."}),i.map((c,n)=>{const d=_[c.type]??"ev-default";return t.jsxs("li",{className:`event-row ${d}`,onClick:()=>l(a===n?null:n),children:[t.jsx("span",{className:"ev-time",children:g(c.ts)}),t.jsx("span",{className:"ev-type",children:c.type}),t.jsx("span",{className:"ev-summary",children:p(c)}),a===n&&t.jsx("pre",{className:"ev-detail",children:JSON.stringify(c,null,2)})]},`${c.ts}-${n}`)})]})]})}const f=["all","compact","recall","config","crew","game"];function $({active:e,onFilter:s}){return t.jsx("div",{className:"event-category-filter",role:"toolbar","aria-label":"Event category filter",children:f.map(r=>t.jsx("button",{type:"button",className:`category-chip${e===r?" active":""}`,onClick:()=>s(r),"aria-pressed":e===r,children:r},r))})}const x=new Set(["compact_start","compact_end","compact_trigger","compact_skip"]),j=new Set(["config_updated","config_preset","tier_changed","model_changed","pressure_lifted","anchors_updated","checkpoint_persisted"]),k=new Set(["crew_presence_changed","crew_turn_changed","crew_bandit_chosen"]),y=new Set(["game_ritual_start","game_ritual_stage","game_ritual_end","game_mode_changed","game_render"]);function N(e,s){if(s==="all")return!0;const r=e.type;return s==="compact"?x.has(r):s==="recall"?r==="recall_inject":s==="config"?j.has(r):s==="crew"?k.has(r):y.has(r)}function E(){const{events:e,status:s,eventCount:r}=u(),[a,l]=o.useState("all"),i=o.useMemo(()=>e.filter(d=>N(d,a)),[e,a]),c=`sse-status sse-${s}`,n=s==="connected"?"connected":s==="connecting"?"connecting…":s==="error"?"error":"disconnected";return t.jsxs("div",{className:"events-tab",children:[t.jsxs("div",{className:"events-header",children:[t.jsxs("span",{className:c,role:"status",children:[t.jsx("span",{className:"sse-dot","aria-hidden":"true"}),n]}),t.jsxs("span",{className:"event-count",children:[r," events received"]})]}),t.jsx($,{active:a,onFilter:l}),t.jsx(h,{events:i})]})}export{E as default};
|
|
2
|
+
//# sourceMappingURL=EventsTab-tYk3VFAp.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EventsTab-CgwYq-Qf.js","sources":["../../src/components/EventStream.tsx","../../src/components/EventCategoryFilter.tsx","../../src/tabs/EventsTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/components/EventStream.tsx — SSE event list with filters.\n *\n * Type badge colored by event type. Timestamp + expandable detail row.\n * Type filter chips (all + per-type). Virtualization: manual windowing for\n * 500+ events (render last N visible).\n */\n\nimport type React from \"react\";\nimport { useState, useMemo } from \"react\";\nimport type { SseEvent } from \"@contracts\";\n\nexport interface EventStreamProps {\n\tevents: SseEvent[];\n}\n\nconst TYPE_COLORS: Record<string, string> = {\n\tcompact_start: \"ev-compact\",\n\tcompact_end: \"ev-compact\",\n\tcompact_trigger: \"ev-trigger\",\n\tcompact_skip: \"ev-trigger\",\n\ttier_changed: \"ev-tier\",\n\tmodel_changed: \"ev-tier\",\n\tpressure_lifted: \"ev-tier\",\n\tcheckpoint_persisted: \"ev-checkpoint\",\n\trecall_inject: \"ev-recall\",\n\tanchors_updated: \"ev-recall\",\n\tconfig_updated: \"ev-config\",\n\tconfig_preset: \"ev-config\",\n\tcrew_presence_changed: \"ev-crew\",\n\tcrew_turn_changed: \"ev-crew\",\n\tcrew_bandit_chosen: \"ev-crew\",\n\tsession_sample: \"ev-recall\",\n\tgame_ritual_start: \"ev-game\",\n\tgame_ritual_stage: \"ev-game\",\n\tgame_ritual_end: \"ev-game\",\n\tgame_mode_changed: \"ev-game\",\n\tgame_render: \"ev-game\",\n};\n\n// Filterable event types (spec: compact_start/end, recall_inject, checkpoint_persisted + all).\nconst FILTER_TYPES = [\n\t\"all\",\n\t\"compact_start\",\n\t\"compact_end\",\n\t\"recall_inject\",\n\t\"checkpoint_persisted\",\n] as const;\ntype FilterType = (typeof FILTER_TYPES)[number];\n\nconst RENDER_WINDOW = 200; // render last 200 to keep DOM light under 500+ events\n\nfunction summarize(ev: SseEvent): string {\n\tswitch (ev.type) {\n\t\tcase \"compact_start\":\n\t\t\treturn `trigger=${ev.trigger} session=${ev.sessionId.slice(0, 8)}`;\n\t\tcase \"compact_end\":\n\t\t\treturn `freed=${ev.tokensFreed} ok=${ev.success} cp=${ev.checkpointId.slice(0, 8)}`;\n\t\tcase \"compact_trigger\":\n\t\t\treturn `pressure=${ev.pressure}% threshold=${ev.threshold}% armed=${ev.armed}`;\n\t\tcase \"compact_skip\":\n\t\t\treturn `reason=${ev.reason}`;\n\t\tcase \"tier_changed\":\n\t\t\treturn `${ev.from} → ${ev.to} ctx=${ev.contextPct}%`;\n\t\tcase \"model_changed\":\n\t\t\treturn `${ev.providerName}/${ev.model}`;\n\t\tcase \"pressure_lifted\":\n\t\t\treturn `${ev.beforePct}% → ${ev.afterPct}%`;\n\t\tcase \"checkpoint_persisted\":\n\t\t\treturn `cp=${ev.checkpointId.slice(0, 8)} sessionTok=${ev.sessionTokens}`;\n\t\tcase \"recall_inject\":\n\t\t\treturn `q=\"${ev.query.slice(0, 40)}\" chunks=${ev.chunks} tok=${ev.tokens}`;\n\t\tcase \"anchors_updated\":\n\t\t\treturn `count=${ev.count} pinned=${ev.pinned}`;\n\t\tcase \"config_updated\":\n\t\t\treturn `key=${ev.key}`;\n\t\tcase \"config_preset\":\n\t\t\treturn `preset=${ev.preset}`;\n\t\tcase \"crew_presence_changed\":\n\t\t\treturn `agents=${ev.activeAgents} turn=${ev.currentTurn}`;\n\t\tcase \"crew_turn_changed\":\n\t\t\treturn `turn=${ev.turnIndex} agent=${ev.agentName}`;\n\t\tcase \"crew_bandit_chosen\":\n\t\t\treturn `agent=${ev.chosenAgent} score=${ev.score} regret=${ev.regret}`;\n\t\tcase \"session_sample\":\n\t\t\treturn `sess=${ev.sessionId.slice(0, 8)} tok=${ev.tokens.toLocaleString()} ctx=${Math.round(ev.percent)}%`;\n\t\tcase \"game_ritual_start\":\n\t\tcase \"game_ritual_stage\":\n\t\tcase \"game_ritual_end\":\n\t\tcase \"game_mode_changed\":\n\t\tcase \"game_render\":\n\t\t\treturn `stage=${ev.type}`;\n\t}\n}\n\nfunction formatTs(ts: string): string {\n\ttry {\n\t\tconst d = new Date(ts);\n\t\treturn d.toLocaleTimeString();\n\t} catch {\n\t\treturn ts;\n\t}\n}\n\nexport function EventStream({ events }: EventStreamProps): React.ReactElement {\n\tconst [filter, setFilter] = useState<FilterType>(\"all\");\n\tconst [expanded, setExpanded] = useState<number | null>(null);\n\n\tconst filtered = useMemo(() => {\n\t\tconst list =\n\t\t\tfilter === \"all\" ? events : events.filter((e) => e.type === filter);\n\t\t// Newest first; render only the last RENDER_WINDOW to keep DOM light.\n\t\treturn list.slice(-RENDER_WINDOW).reverse();\n\t}, [events, filter]);\n\n\treturn (\n\t\t<div className=\"event-stream\">\n\t\t\t<div className=\"event-filters\" role=\"toolbar\">\n\t\t\t\t{FILTER_TYPES.map((t) => (\n\t\t\t\t\t<button\n\t\t\t\t\t\tkey={t}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclassName={`filter-chip ${filter === t ? \"active\" : \"\"}`}\n\t\t\t\t\t\tonClick={() => setFilter(t)}\n\t\t\t\t\t>\n\t\t\t\t\t\t{t === \"all\" ? \"all\" : t.replace(/_/g, \" \")}\n\t\t\t\t\t</button>\n\t\t\t\t))}\n\t\t\t\t<span className=\"event-count\">{events.length} buffered</span>\n\t\t\t</div>\n\t\t\t<ul className=\"event-list\">\n\t\t\t\t{filtered.length === 0 && (\n\t\t\t\t\t<li className=\"event-empty\">No events yet.</li>\n\t\t\t\t)}\n\t\t\t\t{filtered.map((ev, idx) => {\n\t\t\t\t\tconst cls = TYPE_COLORS[ev.type] ?? \"ev-default\";\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tkey={`${ev.ts}-${idx}`}\n\t\t\t\t\t\t\tclassName={`event-row ${cls}`}\n\t\t\t\t\t\t\tonClick={() => setExpanded(expanded === idx ? null : idx)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<span className=\"ev-time\">{formatTs(ev.ts)}</span>\n\t\t\t\t\t\t\t<span className=\"ev-type\">{ev.type}</span>\n\t\t\t\t\t\t\t<span className=\"ev-summary\">{summarize(ev)}</span>\n\t\t\t\t\t\t\t{expanded === idx && (\n\t\t\t\t\t\t\t\t<pre className=\"ev-detail\">{JSON.stringify(ev, null, 2)}</pre>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</li>\n\t\t\t\t\t);\n\t\t\t\t})}\n\t\t\t</ul>\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/components/EventCategoryFilter.tsx\n *\n * Category-level filter chips for the Events tab. Filters SSE events by\n * category: all / compact / recall / config / crew / game.\n */\n\nimport type React from \"react\";\n\nexport type EventCategory =\n\t| \"all\"\n\t| \"compact\"\n\t| \"recall\"\n\t| \"config\"\n\t| \"crew\"\n\t| \"game\";\n\nconst CATEGORIES: readonly EventCategory[] = [\n\t\"all\",\n\t\"compact\",\n\t\"recall\",\n\t\"config\",\n\t\"crew\",\n\t\"game\",\n] as const;\n\nexport interface EventCategoryFilterProps {\n\t/** Currently active category. */\n\tactive: EventCategory;\n\t/** Called when the user selects a category. */\n\tonFilter: (category: EventCategory) => void;\n}\n\nexport function EventCategoryFilter({\n\tactive,\n\tonFilter,\n}: EventCategoryFilterProps): React.ReactElement {\n\treturn (\n\t\t<div className=\"event-category-filter\" role=\"toolbar\" aria-label=\"Event category filter\">\n\t\t\t{CATEGORIES.map((cat) => (\n\t\t\t\t<button\n\t\t\t\t\tkey={cat}\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclassName={`category-chip${active === cat ? \" active\" : \"\"}`}\n\t\t\t\t\tonClick={() => onFilter(cat)}\n\t\t\t\t\taria-pressed={active === cat}\n\t\t\t\t>\n\t\t\t\t\t{cat}\n\t\t\t\t</button>\n\t\t\t))}\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/tabs/EventsTab.tsx — Events tab (C1).\n *\n * Live SSE event stream via useSSE + EventStream with category filter chips.\n * Shows connection status + event count. Category filter uses the existing\n * EventCategoryFilter component to filter by all/compact/recall/config/crew/game.\n */\n\nimport type React from \"react\";\nimport { useState, useMemo } from \"react\";\nimport type { SseEvent } from \"@contracts\";\nimport { useSSE } from \"../hooks/useSSE\";\nimport { EventStream } from \"../components/EventStream\";\nimport {\n\tEventCategoryFilter,\n\ttype EventCategory,\n} from \"../components/EventCategoryFilter\";\n\nconst COMPACT_TYPES = new Set([\n\t\"compact_start\",\n\t\"compact_end\",\n\t\"compact_trigger\",\n\t\"compact_skip\",\n]);\nconst CONFIG_TYPES = new Set([\n\t\"config_updated\",\n\t\"config_preset\",\n\t\"tier_changed\",\n\t\"model_changed\",\n\t\"pressure_lifted\",\n\t\"anchors_updated\",\n\t\"checkpoint_persisted\",\n]);\nconst CREW_TYPES = new Set([\n\t\"crew_presence_changed\",\n\t\"crew_turn_changed\",\n\t\"crew_bandit_chosen\",\n]);\nconst GAME_TYPES = new Set([\n\t\"game_ritual_start\",\n\t\"game_ritual_stage\",\n\t\"game_ritual_end\",\n\t\"game_mode_changed\",\n\t\"game_render\",\n]);\n\nfunction matchesCategory(ev: SseEvent, cat: EventCategory): boolean {\n\tif (cat === \"all\") return true;\n\tconst t = ev.type;\n\tif (cat === \"compact\") return COMPACT_TYPES.has(t);\n\tif (cat === \"recall\") return t === \"recall_inject\";\n\tif (cat === \"config\") return CONFIG_TYPES.has(t);\n\tif (cat === \"crew\") return CREW_TYPES.has(t);\n\treturn GAME_TYPES.has(t); // cat === \"game\"\n}\n\nexport default function EventsTab(): React.ReactElement {\n\tconst { events, status, eventCount } = useSSE();\n\tconst [category, setCategory] = useState<EventCategory>(\"all\");\n\n\tconst filtered = useMemo(\n\t\t() => events.filter((e) => matchesCategory(e, category)),\n\t\t[events, category],\n\t);\n\n\tconst statusCls = `sse-status sse-${status}`;\n\tconst statusLabel =\n\t\tstatus === \"connected\"\n\t\t\t? \"connected\"\n\t\t\t: status === \"connecting\"\n\t\t\t\t? \"connecting…\"\n\t\t\t\t: status === \"error\"\n\t\t\t\t\t? \"error\"\n\t\t\t\t\t: \"disconnected\";\n\n\treturn (\n\t\t<div className=\"events-tab\">\n\t\t\t<div className=\"events-header\">\n\t\t\t\t<span className={statusCls} role=\"status\">\n\t\t\t\t\t<span className=\"sse-dot\" aria-hidden=\"true\" />\n\t\t\t\t\t{statusLabel}\n\t\t\t\t</span>\n\t\t\t\t<span className=\"event-count\">{eventCount} events received</span>\n\t\t\t</div>\n\t\t\t<EventCategoryFilter active={category} onFilter={setCategory} />\n\t\t\t<EventStream events={filtered} />\n\t\t</div>\n\t);\n}\n"],"names":["TYPE_COLORS","FILTER_TYPES","summarize","ev","formatTs","ts","EventStream","events","filter","setFilter","useState","expanded","setExpanded","filtered","useMemo","e","jsxs","t","jsx","idx","cls","CATEGORIES","EventCategoryFilter","active","onFilter","cat","COMPACT_TYPES","CONFIG_TYPES","CREW_TYPES","GAME_TYPES","matchesCategory","EventsTab","status","eventCount","useSSE","category","setCategory","statusCls","statusLabel"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EventsTab-tYk3VFAp.js","sources":["../../src/components/EventStream.tsx","../../src/components/EventCategoryFilter.tsx","../../src/tabs/EventsTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/components/EventStream.tsx — SSE event list with filters.\n *\n * Type badge colored by event type. Timestamp + expandable detail row.\n * Type filter chips (all + per-type). Virtualization: manual windowing for\n * 500+ events (render last N visible).\n */\n\nimport type React from \"react\";\nimport { useState, useMemo } from \"react\";\nimport type { SseEvent } from \"@contracts\";\n\nexport interface EventStreamProps {\n\tevents: SseEvent[];\n}\n\nconst TYPE_COLORS: Record<string, string> = {\n\tcompact_start: \"ev-compact\",\n\tcompact_end: \"ev-compact\",\n\tcompact_trigger: \"ev-trigger\",\n\tcompact_skip: \"ev-trigger\",\n\ttier_changed: \"ev-tier\",\n\tmodel_changed: \"ev-tier\",\n\tpressure_lifted: \"ev-tier\",\n\tcheckpoint_persisted: \"ev-checkpoint\",\n\trecall_inject: \"ev-recall\",\n\tanchors_updated: \"ev-recall\",\n\tconfig_updated: \"ev-config\",\n\tconfig_preset: \"ev-config\",\n\tcrew_presence_changed: \"ev-crew\",\n\tcrew_turn_changed: \"ev-crew\",\n\tcrew_bandit_chosen: \"ev-crew\",\n\tsession_sample: \"ev-recall\",\n\tgame_ritual_start: \"ev-game\",\n\tgame_ritual_stage: \"ev-game\",\n\tgame_ritual_end: \"ev-game\",\n\tgame_mode_changed: \"ev-game\",\n\tgame_render: \"ev-game\",\n};\n\n// Filterable event types (spec: compact_start/end, recall_inject, checkpoint_persisted + all).\nconst FILTER_TYPES = [\n\t\"all\",\n\t\"compact_start\",\n\t\"compact_end\",\n\t\"recall_inject\",\n\t\"checkpoint_persisted\",\n] as const;\ntype FilterType = (typeof FILTER_TYPES)[number];\n\nconst RENDER_WINDOW = 200; // render last 200 to keep DOM light under 500+ events\n\nfunction summarize(ev: SseEvent): string {\n\tswitch (ev.type) {\n\t\tcase \"compact_start\":\n\t\t\treturn `trigger=${ev.trigger} session=${ev.sessionId.slice(0, 8)}`;\n\t\tcase \"compact_end\":\n\t\t\treturn `freed=${ev.tokensFreed} ok=${ev.success} cp=${ev.checkpointId.slice(0, 8)}`;\n\t\tcase \"compact_trigger\":\n\t\t\treturn `pressure=${ev.pressure}% threshold=${ev.threshold}% armed=${ev.armed}`;\n\t\tcase \"compact_skip\":\n\t\t\treturn `reason=${ev.reason}`;\n\t\tcase \"tier_changed\":\n\t\t\treturn `${ev.from} → ${ev.to} ctx=${ev.contextPct}%`;\n\t\tcase \"model_changed\":\n\t\t\treturn `${ev.providerName}/${ev.model}`;\n\t\tcase \"pressure_lifted\":\n\t\t\treturn `${ev.beforePct}% → ${ev.afterPct}%`;\n\t\tcase \"checkpoint_persisted\":\n\t\t\treturn `cp=${ev.checkpointId.slice(0, 8)} sessionTok=${ev.sessionTokens}`;\n\t\tcase \"recall_inject\":\n\t\t\treturn `q=\"${ev.query.slice(0, 40)}\" chunks=${ev.chunks} tok=${ev.tokens}`;\n\t\tcase \"anchors_updated\":\n\t\t\treturn `count=${ev.count} pinned=${ev.pinned}`;\n\t\tcase \"config_updated\":\n\t\t\treturn `key=${ev.key}`;\n\t\tcase \"config_preset\":\n\t\t\treturn `preset=${ev.preset}`;\n\t\tcase \"crew_presence_changed\":\n\t\t\treturn `agents=${ev.activeAgents} turn=${ev.currentTurn}`;\n\t\tcase \"crew_turn_changed\":\n\t\t\treturn `turn=${ev.turnIndex} agent=${ev.agentName}`;\n\t\tcase \"crew_bandit_chosen\":\n\t\t\treturn `agent=${ev.chosenAgent} score=${ev.score} regret=${ev.regret}`;\n\t\tcase \"session_sample\":\n\t\t\treturn `sess=${ev.sessionId.slice(0, 8)} tok=${ev.tokens.toLocaleString()} ctx=${Math.round(ev.percent)}%`;\n\t\tcase \"game_ritual_start\":\n\t\tcase \"game_ritual_stage\":\n\t\tcase \"game_ritual_end\":\n\t\tcase \"game_mode_changed\":\n\t\tcase \"game_render\":\n\t\t\treturn `stage=${ev.type}`;\n\t}\n}\n\nfunction formatTs(ts: string): string {\n\ttry {\n\t\tconst d = new Date(ts);\n\t\treturn d.toLocaleTimeString();\n\t} catch {\n\t\treturn ts;\n\t}\n}\n\nexport function EventStream({ events }: EventStreamProps): React.ReactElement {\n\tconst [filter, setFilter] = useState<FilterType>(\"all\");\n\tconst [expanded, setExpanded] = useState<number | null>(null);\n\n\tconst filtered = useMemo(() => {\n\t\tconst list =\n\t\t\tfilter === \"all\" ? events : events.filter((e) => e.type === filter);\n\t\t// Newest first; render only the last RENDER_WINDOW to keep DOM light.\n\t\treturn list.slice(-RENDER_WINDOW).reverse();\n\t}, [events, filter]);\n\n\treturn (\n\t\t<div className=\"event-stream\">\n\t\t\t<div className=\"event-filters\" role=\"toolbar\">\n\t\t\t\t{FILTER_TYPES.map((t) => (\n\t\t\t\t\t<button\n\t\t\t\t\t\tkey={t}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclassName={`filter-chip ${filter === t ? \"active\" : \"\"}`}\n\t\t\t\t\t\tonClick={() => setFilter(t)}\n\t\t\t\t\t>\n\t\t\t\t\t\t{t === \"all\" ? \"all\" : t.replace(/_/g, \" \")}\n\t\t\t\t\t</button>\n\t\t\t\t))}\n\t\t\t\t<span className=\"event-count\">{events.length} buffered</span>\n\t\t\t</div>\n\t\t\t<ul className=\"event-list\">\n\t\t\t\t{filtered.length === 0 && (\n\t\t\t\t\t<li className=\"event-empty\">No events yet.</li>\n\t\t\t\t)}\n\t\t\t\t{filtered.map((ev, idx) => {\n\t\t\t\t\tconst cls = TYPE_COLORS[ev.type] ?? \"ev-default\";\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tkey={`${ev.ts}-${idx}`}\n\t\t\t\t\t\t\tclassName={`event-row ${cls}`}\n\t\t\t\t\t\t\tonClick={() => setExpanded(expanded === idx ? null : idx)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<span className=\"ev-time\">{formatTs(ev.ts)}</span>\n\t\t\t\t\t\t\t<span className=\"ev-type\">{ev.type}</span>\n\t\t\t\t\t\t\t<span className=\"ev-summary\">{summarize(ev)}</span>\n\t\t\t\t\t\t\t{expanded === idx && (\n\t\t\t\t\t\t\t\t<pre className=\"ev-detail\">{JSON.stringify(ev, null, 2)}</pre>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</li>\n\t\t\t\t\t);\n\t\t\t\t})}\n\t\t\t</ul>\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/components/EventCategoryFilter.tsx\n *\n * Category-level filter chips for the Events tab. Filters SSE events by\n * category: all / compact / recall / config / crew / game.\n */\n\nimport type React from \"react\";\n\nexport type EventCategory =\n\t| \"all\"\n\t| \"compact\"\n\t| \"recall\"\n\t| \"config\"\n\t| \"crew\"\n\t| \"game\";\n\nconst CATEGORIES: readonly EventCategory[] = [\n\t\"all\",\n\t\"compact\",\n\t\"recall\",\n\t\"config\",\n\t\"crew\",\n\t\"game\",\n] as const;\n\nexport interface EventCategoryFilterProps {\n\t/** Currently active category. */\n\tactive: EventCategory;\n\t/** Called when the user selects a category. */\n\tonFilter: (category: EventCategory) => void;\n}\n\nexport function EventCategoryFilter({\n\tactive,\n\tonFilter,\n}: EventCategoryFilterProps): React.ReactElement {\n\treturn (\n\t\t<div className=\"event-category-filter\" role=\"toolbar\" aria-label=\"Event category filter\">\n\t\t\t{CATEGORIES.map((cat) => (\n\t\t\t\t<button\n\t\t\t\t\tkey={cat}\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclassName={`category-chip${active === cat ? \" active\" : \"\"}`}\n\t\t\t\t\tonClick={() => onFilter(cat)}\n\t\t\t\t\taria-pressed={active === cat}\n\t\t\t\t>\n\t\t\t\t\t{cat}\n\t\t\t\t</button>\n\t\t\t))}\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/tabs/EventsTab.tsx — Events tab (C1).\n *\n * Live SSE event stream via useSSE + EventStream with category filter chips.\n * Shows connection status + event count. Category filter uses the existing\n * EventCategoryFilter component to filter by all/compact/recall/config/crew/game.\n */\n\nimport type React from \"react\";\nimport { useState, useMemo } from \"react\";\nimport type { SseEvent } from \"@contracts\";\nimport { useSSE } from \"../hooks/useSSE\";\nimport { EventStream } from \"../components/EventStream\";\nimport {\n\tEventCategoryFilter,\n\ttype EventCategory,\n} from \"../components/EventCategoryFilter\";\n\nconst COMPACT_TYPES = new Set([\n\t\"compact_start\",\n\t\"compact_end\",\n\t\"compact_trigger\",\n\t\"compact_skip\",\n]);\nconst CONFIG_TYPES = new Set([\n\t\"config_updated\",\n\t\"config_preset\",\n\t\"tier_changed\",\n\t\"model_changed\",\n\t\"pressure_lifted\",\n\t\"anchors_updated\",\n\t\"checkpoint_persisted\",\n]);\nconst CREW_TYPES = new Set([\n\t\"crew_presence_changed\",\n\t\"crew_turn_changed\",\n\t\"crew_bandit_chosen\",\n]);\nconst GAME_TYPES = new Set([\n\t\"game_ritual_start\",\n\t\"game_ritual_stage\",\n\t\"game_ritual_end\",\n\t\"game_mode_changed\",\n\t\"game_render\",\n]);\n\nfunction matchesCategory(ev: SseEvent, cat: EventCategory): boolean {\n\tif (cat === \"all\") return true;\n\tconst t = ev.type;\n\tif (cat === \"compact\") return COMPACT_TYPES.has(t);\n\tif (cat === \"recall\") return t === \"recall_inject\";\n\tif (cat === \"config\") return CONFIG_TYPES.has(t);\n\tif (cat === \"crew\") return CREW_TYPES.has(t);\n\treturn GAME_TYPES.has(t); // cat === \"game\"\n}\n\nexport default function EventsTab(): React.ReactElement {\n\tconst { events, status, eventCount } = useSSE();\n\tconst [category, setCategory] = useState<EventCategory>(\"all\");\n\n\tconst filtered = useMemo(\n\t\t() => events.filter((e) => matchesCategory(e, category)),\n\t\t[events, category],\n\t);\n\n\tconst statusCls = `sse-status sse-${status}`;\n\tconst statusLabel =\n\t\tstatus === \"connected\"\n\t\t\t? \"connected\"\n\t\t\t: status === \"connecting\"\n\t\t\t\t? \"connecting…\"\n\t\t\t\t: status === \"error\"\n\t\t\t\t\t? \"error\"\n\t\t\t\t\t: \"disconnected\";\n\n\treturn (\n\t\t<div className=\"events-tab\">\n\t\t\t<div className=\"events-header\">\n\t\t\t\t<span className={statusCls} role=\"status\">\n\t\t\t\t\t<span className=\"sse-dot\" aria-hidden=\"true\" />\n\t\t\t\t\t{statusLabel}\n\t\t\t\t</span>\n\t\t\t\t<span className=\"event-count\">{eventCount} events received</span>\n\t\t\t</div>\n\t\t\t<EventCategoryFilter active={category} onFilter={setCategory} />\n\t\t\t<EventStream events={filtered} />\n\t\t</div>\n\t);\n}\n"],"names":["TYPE_COLORS","FILTER_TYPES","summarize","ev","formatTs","ts","EventStream","events","filter","setFilter","useState","expanded","setExpanded","filtered","useMemo","e","jsxs","t","jsx","idx","cls","CATEGORIES","EventCategoryFilter","active","onFilter","cat","COMPACT_TYPES","CONFIG_TYPES","CREW_TYPES","GAME_TYPES","matchesCategory","EventsTab","status","eventCount","useSSE","category","setCategory","statusCls","statusLabel"],"mappings":"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"}
|