pi-mega-compact 0.12.6 → 0.13.0
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/gather-debug.js +26 -0
- package/dist/extensions/dashboard-server/routes-setup.js +20 -1
- package/dist/extensions/mega-events/agent-handlers.js +16 -3
- package/dist/extensions/mega-runtime/env-loader.js +6 -1
- package/dist/src/httpEmbedder.js +24 -11
- package/extensions/dashboard-client/dist/assets/{CacheTab-BfGJkiaf.js → CacheTab-BZNnKhh5.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-BfGJkiaf.js.map → CacheTab-BZNnKhh5.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ConfigTab-DV08rHoV.js → ConfigTab-DG0WkuuG.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ConfigTab-DV08rHoV.js.map → ConfigTab-DG0WkuuG.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-DHytZ0xs.js → EventsTab-CzF5L42U.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-DHytZ0xs.js.map → EventsTab-CzF5L42U.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-B0klAxD8.js → HealthTab-BLpidMrX.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-B0klAxD8.js.map → HealthTab-BLpidMrX.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-CHKh3KPI.js → MaintenanceTab-BQF7W7xX.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-CHKh3KPI.js.map → MaintenanceTab-BQF7W7xX.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-B6FLQ-32.js → MemoryMapTab-CPtHLv0A.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-B6FLQ-32.js.map → MemoryMapTab-CPtHLv0A.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-DvOjTDkW.js → MetricsTab-B0PCysqs.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-DvOjTDkW.js.map → MetricsTab-B0PCysqs.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-CqbONEwi.js → OverviewTab-DsOhqFu4.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-CqbONEwi.js.map → OverviewTab-DsOhqFu4.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-sc1GLn7i.js → ReposTab-B3YKYuOf.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-sc1GLn7i.js.map → ReposTab-B3YKYuOf.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-C5gB45oc.js → SessionsTab-B96FWcrC.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-C5gB45oc.js.map → SessionsTab-B96FWcrC.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/SetupTab-hbzQ_NZv.js +2 -0
- package/extensions/dashboard-client/dist/assets/SetupTab-hbzQ_NZv.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BBwvb4cC.js → TimeSavedCard-BfkUVTvA.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BBwvb4cC.js.map → TimeSavedCard-BfkUVTvA.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TopicsTab-BsCf512_.js → TopicsTab-DLRsZmXJ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TopicsTab-BsCf512_.js.map → TopicsTab-DLRsZmXJ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-C3ti7NB5.js → TurnsTab-DKGU2Bf6.js} +2 -2
- package/extensions/dashboard-client/dist/assets/TurnsTab-DKGU2Bf6.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{index-BEeaI9uo.js → index-D-lPLRWB.js} +11 -11
- package/extensions/dashboard-client/dist/assets/{index-BEeaI9uo.js.map → index-D-lPLRWB.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-BMSSO9wB.js → useSSE-ClbbcgIp.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-BMSSO9wB.js.map → useSSE-ClbbcgIp.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/App.tsx +1 -8
- package/extensions/dashboard-client/src/tabs/SetupTab.tsx +118 -3
- package/extensions/dashboard-client/src/tabs/TurnsTab.tsx +2 -2
- package/extensions/dashboard-server/api-contracts/setup.ts +5 -3
- package/extensions/dashboard-server/gather-debug.ts +21 -0
- package/extensions/dashboard-server/routes-setup.ts +19 -2
- package/extensions/mega-events/agent-handlers.ts +16 -3
- package/extensions/mega-runtime/env-loader.ts +6 -1
- package/package.json +3 -1
- package/src/httpEmbedder.ts +25 -11
- package/extensions/dashboard-client/dist/assets/AchievementTiles-Cntu89Xw.js +0 -2
- package/extensions/dashboard-client/dist/assets/AchievementTiles-Cntu89Xw.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/AchievementsTab-4ckY9z4n.js +0 -2
- package/extensions/dashboard-client/dist/assets/AchievementsTab-4ckY9z4n.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/GameTab-Ofpoz-6s.js +0 -2
- package/extensions/dashboard-client/dist/assets/GameTab-Ofpoz-6s.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/SetupTab-BEEX4OF5.js +0 -2
- package/extensions/dashboard-client/dist/assets/SetupTab-BEEX4OF5.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/TurnsTab-C3ti7NB5.js.map +0 -1
|
@@ -153,6 +153,32 @@ export function sendGatherDebug(res, ctx) {
|
|
|
153
153
|
audit[`turns.${table}`] = "table missing";
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
|
+
// Turns-schema diagnostic: detect the v1 pressure_band constraint
|
|
157
|
+
// (the root cause of the "turns blank" bug — pre-v0.12.2 schema only
|
|
158
|
+
// allowed 'green'/'yellow'/'red' but the runtime sends 'low'/'medium'/
|
|
159
|
+
// 'high'/'ultra'/'mega', so every INSERT failed silently). If this
|
|
160
|
+
// shows "v1 (broken)", the migration did not run and turns can't be
|
|
161
|
+
// written until the extension is updated + pi restarted.
|
|
162
|
+
try {
|
|
163
|
+
const tSql = tdb
|
|
164
|
+
.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='turns'")
|
|
165
|
+
.get();
|
|
166
|
+
const sql = tSql?.sql ?? "";
|
|
167
|
+
if (sql.includes("'low'"))
|
|
168
|
+
audit["turns.schema"] = "v2 (widened)";
|
|
169
|
+
else if (sql.includes("'green'"))
|
|
170
|
+
audit["turns.schema"] = "v1 (broken — needs update+restart)";
|
|
171
|
+
else if (sql)
|
|
172
|
+
audit["turns.schema"] = "unknown";
|
|
173
|
+
else
|
|
174
|
+
audit["turns.schema"] = "table absent";
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
audit["turns.schema"] = "check failed";
|
|
178
|
+
}
|
|
179
|
+
// Whether the turns.db path is enabled (if false, turns go to the
|
|
180
|
+
// legacy main-db path and turns.db stays empty → blank Turns tab).
|
|
181
|
+
audit["turns.dbEnabled"] = process.env.MEGACOMPACT_TURNS_DB ?? "true (default)";
|
|
156
182
|
}
|
|
157
183
|
catch (e) {
|
|
158
184
|
audit["turns.db"] = `error: ${e instanceof Error ? e.message : String(e)}`;
|
|
@@ -237,17 +237,29 @@ export function handleSetupConfigure(req, res, ctx) {
|
|
|
237
237
|
}
|
|
238
238
|
const body = parsed.value;
|
|
239
239
|
const embedder = body.embedder;
|
|
240
|
-
if (embedder !== "ollama" && embedder !== "llama" && embedder !== "trigram") {
|
|
240
|
+
if (embedder !== "ollama" && embedder !== "llama" && embedder !== "trigram" && embedder !== "custom") {
|
|
241
241
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
242
242
|
res.end(JSON.stringify({ error: "invalid_embedder" }));
|
|
243
243
|
return;
|
|
244
244
|
}
|
|
245
245
|
const url = body.url;
|
|
246
246
|
let resolvedUrl;
|
|
247
|
+
let allowRemote = false;
|
|
247
248
|
if (embedder === "ollama")
|
|
248
249
|
resolvedUrl = typeof url === "string" && url ? url : OLLAMA_DEFAULT_URL;
|
|
249
250
|
else if (embedder === "llama")
|
|
250
251
|
resolvedUrl = typeof url === "string" && url ? url : LLAMA_DEFAULT_URL;
|
|
252
|
+
else if (embedder === "custom") {
|
|
253
|
+
// Third-party / remote endpoint. Requires a URL; opts in to the
|
|
254
|
+
// non-loopback allowlist so embeddingConfigFromEnv() accepts it.
|
|
255
|
+
if (typeof url !== "string" || !url) {
|
|
256
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
257
|
+
res.end(JSON.stringify({ error: "custom_embedder_requires_url" }));
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
resolvedUrl = url;
|
|
261
|
+
allowRemote = true;
|
|
262
|
+
}
|
|
251
263
|
else
|
|
252
264
|
resolvedUrl = null;
|
|
253
265
|
// Write .mega-compact.env to the state dir (loaded by env-loader at next startup).
|
|
@@ -259,11 +271,18 @@ export function handleSetupConfigure(req, res, ctx) {
|
|
|
259
271
|
];
|
|
260
272
|
if (resolvedUrl) {
|
|
261
273
|
lines.push(`export MEGACOMPACT_EMBEDDING_URL="${resolvedUrl}"`);
|
|
274
|
+
if (allowRemote) {
|
|
275
|
+
lines.push(`export MEGACOMPACT_ALLOW_REMOTE_EMBEDDER="1"`);
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
lines.push(`# MEGACOMPACT_ALLOW_REMOTE_EMBEDDER not set (loopback-only)`);
|
|
279
|
+
}
|
|
262
280
|
}
|
|
263
281
|
else {
|
|
264
282
|
lines.push("# trigram: built-in embedder, no URL needed");
|
|
265
283
|
lines.push("# unset MEGACOMPACT_EMBEDDING_URL (commented to override any shell-set value)");
|
|
266
284
|
lines.push("# export MEGACOMPACT_EMBEDDING_URL=");
|
|
285
|
+
lines.push("# export MEGACOMPACT_ALLOW_REMOTE_EMBEDDER=");
|
|
267
286
|
}
|
|
268
287
|
lines.push("");
|
|
269
288
|
try {
|
|
@@ -265,7 +265,9 @@ export function registerAgentHandlers(pi, runtime, config) {
|
|
|
265
265
|
}
|
|
266
266
|
// S43 (per-turn tracking): record one turn row with the cached metrics so
|
|
267
267
|
// the turn layer is queryable + forkable. Best-effort + non-fatal: a write
|
|
268
|
-
// failure never breaks the agent loop.
|
|
268
|
+
// failure never breaks the agent loop. The catch logs the error (structured
|
|
269
|
+
// JSON line to events.log) so a recurring turn-write failure is diagnosable
|
|
270
|
+
// instead of silently producing a blank Turns tab.
|
|
269
271
|
try {
|
|
270
272
|
const convId = ensureConversationIdFor(config, runtime.rt.sessionId, runtime.currentStateDir);
|
|
271
273
|
recordTurnWrite(config, {
|
|
@@ -280,9 +282,20 @@ export function registerAgentHandlers(pi, runtime, config) {
|
|
|
280
282
|
pressureBand: runtime.pressureBand ?? undefined,
|
|
281
283
|
modelId: runtime.currentModel?.modelId ?? undefined,
|
|
282
284
|
}, runtime.currentStateDir);
|
|
285
|
+
runtime.dashboard.event("turn_written", {
|
|
286
|
+
turnIndex: event.turnIndex,
|
|
287
|
+
conversationId: convId,
|
|
288
|
+
pressureBand: runtime.pressureBand ?? null,
|
|
289
|
+
turnsDbEnabled: config.turnsDbEnabled,
|
|
290
|
+
});
|
|
283
291
|
}
|
|
284
|
-
catch {
|
|
285
|
-
|
|
292
|
+
catch (e) {
|
|
293
|
+
runtime.dashboard.event("turn_write_failed", {
|
|
294
|
+
turnIndex: event.turnIndex,
|
|
295
|
+
turnsDbEnabled: config.turnsDbEnabled,
|
|
296
|
+
error: e instanceof Error ? e.message : String(e),
|
|
297
|
+
stack: e instanceof Error ? e.stack?.split("\n").slice(0, 3).join(" | ") : undefined,
|
|
298
|
+
});
|
|
286
299
|
}
|
|
287
300
|
// S33: game-mode scoring — record turns + cache metrics per repo, and arm
|
|
288
301
|
// the MEGA CACHE flare (oopsie gag) when the real dedup hit rate exceeds
|
|
@@ -40,7 +40,12 @@ export function loadMegaEnv(stateDir) {
|
|
|
40
40
|
const eq = trimmed.indexOf("=");
|
|
41
41
|
if (eq < 0)
|
|
42
42
|
continue;
|
|
43
|
-
|
|
43
|
+
let key = trimmed.slice(0, eq).trim();
|
|
44
|
+
// The setup wizard + Setup tab write shell-sourceable syntax
|
|
45
|
+
// (`export KEY="VALUE"`). Strip the leading `export ` so the key is
|
|
46
|
+
// `KEY`, not `export KEY` — otherwise process.env gets a useless entry
|
|
47
|
+
// with a space in the name and the real var is never set.
|
|
48
|
+
key = key.replace(/^export\s+/, "");
|
|
44
49
|
if (!key)
|
|
45
50
|
continue;
|
|
46
51
|
let val = trimmed.slice(eq + 1).trim();
|
package/dist/src/httpEmbedder.js
CHANGED
|
@@ -112,19 +112,32 @@ export function embeddingConfigFromEnv() {
|
|
|
112
112
|
return null;
|
|
113
113
|
}
|
|
114
114
|
const hostname = parsed.hostname.replace(/^\[|\]$/g, ""); // strip IPv6 brackets
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
// Opt-in escape hatch for a remote/third-party embedding endpoint (e.g. a
|
|
116
|
+
// hosted embeddings API). Default OFF — loopback-only (PREVENT-PI-004). When
|
|
117
|
+
// MEGACOMPACT_ALLOW_REMOTE_EMBEDDER=1 the loopback check is skipped, so an
|
|
118
|
+
// advanced user can point at a non-loopback URL. The fetch still goes through
|
|
119
|
+
// HttpEmbedder (guardrails-allowed below) and credentials-in-URL are still
|
|
120
|
+
// rejected. Flag-OFF = byte-identical to the pre-change loopback enforcement.
|
|
121
|
+
const allowRemote = process.env.MEGACOMPACT_ALLOW_REMOTE_EMBEDDER === "1" ||
|
|
122
|
+
process.env.MEGACOMPACT_ALLOW_REMOTE_EMBEDDER === "true";
|
|
123
|
+
if (!allowRemote) {
|
|
124
|
+
if (isIP(hostname)) {
|
|
125
|
+
// Literal IP — must be loopback.
|
|
126
|
+
if (!isLoopbackIP(hostname)) {
|
|
127
|
+
console.warn(`MEGACOMPACT_EMBEDDING_URL must be a loopback IP (got ${hostname}) — falling back to default embedder (PREVENT-PI-004)`);
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
// Hostname — resolve via dns.lookup and require ALL addresses loopback.
|
|
133
|
+
if (!hostnameResolvesToLoopback(hostname)) {
|
|
134
|
+
console.warn(`MEGACOMPACT_EMBEDDING_URL hostname "${hostname}" does not resolve to loopback — falling back to default embedder (PREVENT-PI-004)`);
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
120
137
|
}
|
|
121
138
|
}
|
|
122
139
|
else {
|
|
123
|
-
|
|
124
|
-
if (!hostnameResolvesToLoopback(hostname)) {
|
|
125
|
-
console.warn(`MEGACOMPACT_EMBEDDING_URL hostname "${hostname}" does not resolve to loopback — falling back to default embedder (PREVENT-PI-004)`);
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
140
|
+
console.warn(`MEGACOMPACT_ALLOW_REMOTE_EMBEDDER=1 — allowing non-loopback embedder endpoint ${hostname} (user-opted-in)`);
|
|
128
141
|
}
|
|
129
142
|
const headers = {};
|
|
130
143
|
if (process.env.MEGACOMPACT_EMBEDDING_HEADERS) {
|
|
@@ -169,7 +182,7 @@ function parseEmbedding(body) {
|
|
|
169
182
|
const WORKER = String.raw `
|
|
170
183
|
const u = process.env.MC_URL, b = process.env.MC_BODY, h = JSON.parse(process.env.MC_HEADERS || "{}");
|
|
171
184
|
try {
|
|
172
|
-
const r = await fetch(u, { method: "POST", headers: h, body: b }); // guardrails-allow PREVENT-PI-004:
|
|
185
|
+
const r = await fetch(u, { method: "POST", headers: h, body: b }); // guardrails-allow PREVENT-PI-004: BYO embedding endpoint — loopback-only by default; remote allowed only when MEGACOMPACT_ALLOW_REMOTE_EMBEDDER=1 (user opt-in)
|
|
173
186
|
const out = JSON.stringify({ status: r.status, ok: r.ok, json: await r.json() });
|
|
174
187
|
process.stdout.write(out);
|
|
175
188
|
} catch (e) {
|
package/extensions/dashboard-client/dist/assets/{CacheTab-BfGJkiaf.js → CacheTab-BZNnKhh5.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,m as R,n as P,l as E,c as $}from"./index-D-lPLRWB.js";import{C as L,T as A}from"./TimeSavedCard-BfkUVTvA.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 q(){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{q as default};
|
|
2
|
+
//# sourceMappingURL=CacheTab-BZNnKhh5.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CacheTab-BfGJkiaf.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-BZNnKhh5.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/{ConfigTab-DV08rHoV.js → ConfigTab-DG0WkuuG.js}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as i,p as N,j as e,c as w,d as y}from"./index-
|
|
2
|
-
//# sourceMappingURL=ConfigTab-
|
|
1
|
+
import{r as i,p as N,j as e,c as w,d as y}from"./index-D-lPLRWB.js";const b=[{id:"transparent",label:"Transparent (accents only)",css:{bg:null,fg:"#c9d1d9",accent:"#3fb950",mega:"#f0883e"},ansi:{bg:null,fg:"39",accent:"32",mega:"33"}},{id:"retro",label:"Retro 8-bit green",css:{bg:"#003300",fg:"#33ff33",accent:"#00ff41",mega:"#39ff14"},ansi:{bg:"40",fg:"92",accent:"92",mega:"1;92"}},{id:"orange-bold",label:"Orange bold",css:{bg:"#1a0f00",fg:"#ff8c00",accent:"#ff6b00",mega:"#ff4500"},ansi:{bg:"48;5;52",fg:"38;5;208",accent:"38;5;208",mega:"1;38;5;202"}},{id:"cyan-neon",label:"Cyan neon",css:{bg:"#001a1a",fg:"#00ffff",accent:"#22d3ee",mega:"#7df9ff"},ansi:{bg:"48;5;23",fg:"38;5;51",accent:"38;5;51",mega:"1;38;5;51"}},{id:"amber-mono",label:"Amber phosphor",css:{bg:"#1a1200",fg:"#ffb000",accent:"#ff8c00",mega:"#ff5500"},ansi:{bg:"48;5;58",fg:"38;5;214",accent:"38;5;214",mega:"1;38;5;208"}},{id:"grayscale",label:"Grayscale minimal",css:{bg:"#161616",fg:"#b0b0b0",accent:"#8b949e",mega:"#e6edf3"},ansi:{bg:"48;5;235",fg:"38;5;249",accent:"38;5;249",mega:"1;38;5;255"}}],g=b.map(t=>t.id),c={gameMode:"Turn game mode on/off (themes the widget + dashboard)",theme:"Visual theme (applies instantly)",tuiMode:"TUI widget display density",tier:"Live pressure band — climbs low→mega as context fills the window.",preset:"The env-resolved base compaction preset (low/medium/high/ultra/mega) that set the token threshold.",pressure:"Live pressure = currentTokens / threshold — % of the model context window (threshold fires at the tier's % of window).",threshold:"Compaction threshold = tierPct × model context window — mega-compact trims BELOW pi's native ~80% auto-compact for any model size.",fastGate:"Fast-gate arming floor — the live trim arms once context passes this % of the window."};function T(t,d,l){let o=t.toLocaleString();return d!=null&&l>0&&(o+=` (${Math.round(d*100)}% of ${l.toLocaleString()})`),o}function S(t){return`${Math.round((t||0)*100)}%`}function E(){const[t,d]=i.useState(null),[l,o]=i.useState(null),[m,u]=i.useState(!1);i.useEffect(()=>{let s=!0;const a=async()=>{try{const r=await w();s&&d(r)}catch{}};a();const f=setInterval(()=>void a(),5e3);return()=>{s=!1,clearInterval(f)}},[]),i.useEffect(()=>{let s=!0;const a=async()=>{try{const r=await y();s&&(o(r),r.theme&&(document.documentElement.dataset.theme=r.theme))}catch{}};a();const f=setInterval(()=>void a(),5e3);return()=>{s=!1,clearInterval(f)}},[]);const h=i.useCallback(async s=>{u(!0);try{const a=await N(s);o(a),a.theme&&(document.documentElement.dataset.theme=a.theme)}catch{}u(!1)},[]),x=l?.game_mode_on??!1,p=l?.theme??"transparent",v=l?.tui_display_mode??"full",n=t?.config,j=t?.context.contextWindow??0;return e.jsxs("div",{className:"config-tab",children:[e.jsxs("section",{className:"config-settings",children:[e.jsx("h3",{children:"Settings"}),e.jsx("div",{className:"settings-row",children:e.jsxs("label",{title:c.gameMode,children:[e.jsx("input",{type:"checkbox",checked:x,onChange:s=>h({game_mode_on:s.currentTarget.checked}),disabled:m}),"Game Mode"]})}),e.jsx("div",{className:"settings-row",children:e.jsxs("label",{title:c.theme,children:["Theme",e.jsx("select",{value:p,onChange:s=>h({theme:s.currentTarget.value}),disabled:m,children:b.map(s=>e.jsx("option",{value:s.id,children:s.label},s.id))}),e.jsx("button",{type:"button",disabled:m,className:"theme-cycle-btn",title:"Cycle to the next theme (P3)",onClick:()=>{const s=g.indexOf(p),a=g[(s<0?0:s+1)%g.length];a&&h({theme:a})},children:"Next theme →"})]})}),e.jsx("div",{className:"settings-row",children:e.jsxs("label",{title:c.tuiMode,children:["TUI Mode",e.jsxs("select",{value:v,onChange:s=>h({tui_display_mode:s.currentTarget.value}),disabled:m,children:[e.jsx("option",{value:"full",children:"Full"}),e.jsx("option",{value:"minimal",children:"Minimal"})]})," ",e.jsx("span",{className:"tui-mode-hint",children:"(affects the in-app TUI widget; not shown here)"})]})})]}),e.jsxs("section",{className:"config-display",children:[e.jsx("h3",{children:"Configuration"}),e.jsxs("div",{className:"config-grid",children:[e.jsxs("div",{className:"config-item",children:[e.jsx("span",{className:"config-label",title:c.tier,children:"Tier"}),e.jsx("span",{className:"config-value",children:t?`${t.tier} (live)`:"—"})]}),e.jsxs("div",{className:"config-item",children:[e.jsx("span",{className:"config-label",title:c.preset,children:"Preset"}),e.jsx("span",{className:"config-value",children:t?.presetTier??"—"})]}),e.jsxs("div",{className:"config-item",children:[e.jsx("span",{className:"config-label",title:c.pressure,children:"Pressure"}),e.jsx("span",{className:"config-value",children:t?S(t.pressure):"—"})]}),e.jsxs("div",{className:"config-item",children:[e.jsx("span",{className:"config-label",title:c.threshold,children:"Threshold"}),e.jsx("span",{className:"config-value",children:n?T(n.thresholdTokens,n.tierPct,j):"—"})]}),e.jsxs("div",{className:"config-item",children:[e.jsx("span",{className:"config-label",title:c.fastGate,children:"Fast Gate"}),e.jsx("span",{className:"config-value",children:n?`${n.fastGatePct}%`:"—"})]}),e.jsxs("div",{className:"config-item",children:[e.jsx("span",{className:"config-label",children:"Auto"}),e.jsx("span",{className:"config-value",children:n?n.auto?"enabled":"disabled":"—"})]}),e.jsxs("div",{className:"config-item",children:[e.jsx("span",{className:"config-label",children:"Anchor"}),e.jsx("span",{className:"config-value",children:n?.anchorUserMessages??"—"})]})]})]})]})}export{E as default};
|
|
2
|
+
//# sourceMappingURL=ConfigTab-DG0WkuuG.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConfigTab-DV08rHoV.js","sources":["../../../../src/config/themes.ts","../../src/tabs/ConfigTab.tsx"],"sourcesContent":["/**\n * themes.ts — SINGLE SOURCE OF TRUTH for game-mode visual-effect themes (S30).\n *\n * Each theme defines BOTH an HTML/CSS palette (dashboard) AND an ANSI accent\n * mapping (TUI widget), since the TUI is ANSI and the dashboard is HTML.\n *\n * Theme scope (QA1 correction): themes restyle the mega-compact statusbar\n * widget + the whole dashboard HTML page. We do NOT theme pi's chrome.\n *\n * `transparent` (default) semantics: no background fill, colored accents only —\n * so game-mode stays visible against any terminal background.\n *\n * PREVENT-PI-004: pure config, no network. Pi-agnostic: no pi runtime types.\n */\n\n/** HTML/CSS palette for the dashboard (hex strings, or `null` for \"no bg\"). */\nexport interface ThemeCss {\n /** Page/cell background hex, or `null` for transparent (no bg fill). */\n bg: string | null;\n /** Default foreground (text) hex. */\n fg: string;\n /** Accent hex (bars, highlights, level numbers). */\n accent: string;\n /** MEGA CACHE highlight hex (fires at cache >= 100%). */\n mega: string;\n}\n\n/** ANSI accent mapping for the TUI widget (SGR parameter strings, no `\\x1b[`).\n * `bg` is `null` for transparent themes (no background fill). */\nexport interface ThemeAnsi {\n /** Background SGR params (e.g. \"48;5;23\"), or `null` for no bg fill. */\n bg: string | null;\n /** Foreground SGR params (e.g. \"39\" for default, \"92\" for bright green). */\n fg: string;\n /** Accent SGR params. */\n accent: string;\n /** MEGA CACHE SGR params (fires at cache >= 100%). */\n mega: string;\n}\n\n/** A complete theme = CSS palette + ANSI mapping + display metadata. */\nexport interface Theme {\n id: string;\n label: string;\n css: ThemeCss;\n ansi: ThemeAnsi;\n}\n\nexport const DEFAULT_THEME = \"transparent\";\n\nexport const THEMES: readonly Theme[] = [\n {\n id: \"transparent\",\n label: \"Transparent (accents only)\",\n css: { bg: null, fg: \"#c9d1d9\", accent: \"#3fb950\", mega: \"#f0883e\" },\n ansi: { bg: null, fg: \"39\", accent: \"32\", mega: \"33\" },\n },\n {\n id: \"retro\",\n label: \"Retro 8-bit green\",\n css: { bg: \"#003300\", fg: \"#33ff33\", accent: \"#00ff41\", mega: \"#39ff14\" },\n ansi: { bg: \"40\", fg: \"92\", accent: \"92\", mega: \"1;92\" },\n },\n {\n id: \"orange-bold\",\n label: \"Orange bold\",\n css: { bg: \"#1a0f00\", fg: \"#ff8c00\", accent: \"#ff6b00\", mega: \"#ff4500\" },\n ansi: { bg: \"48;5;52\", fg: \"38;5;208\", accent: \"38;5;208\", mega: \"1;38;5;202\" },\n },\n {\n id: \"cyan-neon\",\n label: \"Cyan neon\",\n css: { bg: \"#001a1a\", fg: \"#00ffff\", accent: \"#22d3ee\", mega: \"#7df9ff\" },\n ansi: { bg: \"48;5;23\", fg: \"38;5;51\", accent: \"38;5;51\", mega: \"1;38;5;51\" },\n },\n {\n id: \"amber-mono\",\n label: \"Amber phosphor\",\n css: { bg: \"#1a1200\", fg: \"#ffb000\", accent: \"#ff8c00\", mega: \"#ff5500\" },\n ansi: { bg: \"48;5;58\", fg: \"38;5;214\", accent: \"38;5;214\", mega: \"1;38;5;208\" },\n },\n {\n id: \"grayscale\",\n label: \"Grayscale minimal\",\n css: { bg: \"#161616\", fg: \"#b0b0b0\", accent: \"#8b949e\", mega: \"#e6edf3\" },\n ansi: { bg: \"48;5;235\", fg: \"38;5;249\", accent: \"38;5;249\", mega: \"1;38;5;255\" },\n },\n] as const;\n\n/** All valid theme ids, in definition order (used for `theme next` cycling). */\nexport const THEME_IDS: readonly string[] = THEMES.map((t) => t.id);\n\n/** Look up a theme by id; returns `undefined` for an unknown id (caller\n * validates + falls back to DEFAULT_THEME). */\nexport function getTheme(id: string): Theme | undefined {\n return THEMES.find((t) => t.id === id);\n}\n\n/** True if `id` is a known theme id. */\nexport function isValidTheme(id: string): boolean {\n return THEME_IDS.includes(id);\n}\n\n/** Cycle to the next theme id (wraps at the end). Falls back to DEFAULT_THEME\n * if `current` is unknown (defensive — the stored id should always be valid). */\nexport function nextTheme(current: string): string {\n const i = THEME_IDS.indexOf(current);\n if (i < 0) return DEFAULT_THEME;\n return THEME_IDS[(i + 1) % THEME_IDS.length]!;\n}\n\n/** CSS variable declarations for a theme's CSS palette. `bg` is `null` for the\n * transparent theme → `--bg: transparent` (no page fill, accents only). Pure,\n * no pi types. */\nexport function themeCssVars(theme: Theme): string {\n const bg = theme.css.bg ?? \"transparent\";\n return `--bg: ${bg}; --fg: ${theme.css.fg}; --accent: ${theme.css.accent}; --mega: ${theme.css.mega};`;\n}\n\n/** A `:root[data-theme=\"<id>\"]` override block carrying the theme's CSS vars,\n * for client-side instant theme switching. Pure, no pi types. */\nexport function themeDataBlock(theme: Theme): string {\n return `:root[data-theme=\"${theme.id}\"]{ ${themeCssVars(theme)} }`;\n}\n","/**\n * dashboard-client/src/tabs/ConfigTab.tsx — Config tab (C3).\n *\n * Full configuration editor:\n * (1) Game mode toggle (checkbox)\n * (2) Theme selector (dropdown) from src/config/themes.ts THEMES array\n * (3) TUI mode selector (Full/Minimal dropdown)\n * (4) Read-only config display section from snapshot (Tier, Preset, Pressure,\n * Threshold, Fast Gate, Auto, Anchor) with verbatim tooltips from html.ts.\n *\n * Uses fetchGameState() to load current state, putGameState() to save changes.\n * Polls gameState and snapshot every 5s. Applies theme by setting\n * document.documentElement.dataset.theme.\n */\n\nimport type React from \"react\";\nimport { useState, useEffect, useCallback } from \"react\";\nimport type { SnapshotResponse, GameStatePatch } from \"@contracts\";\nimport { fetchSnapshot, fetchGameState, putGameState } from \"../api/client\";\nimport { THEMES, THEME_IDS } from \"../../../../src/config/themes\";\n\n/** Actual flat game state returned by the server (the server returns a flat\n * { game_mode_on, theme, tui_display_mode } shape, NOT the contract's nested\n * { config: { enabled, theme, displayMode }, activeRitual } shape). */\ninterface FlatGameState {\n\tgame_mode_on: boolean;\n\ttheme: string;\n\ttui_display_mode: \"full\" | \"minimal\";\n}\ntype FlatGameStatePatch = Partial<FlatGameState>;\n\n/** Extended snapshot config — the runtime includes tierPct (not in contract).\n * Implemented as a type intersection rather than `interface extends` because\n * TypeScript (TS2499) forbids `interface extends T[\"k\"]` indexed-access\n * parents; the resulting type is structurally identical. */\ntype ConfigExt = SnapshotResponse[\"config\"] & {\n\ttierPct?: number | null;\n};\n\n/* Tooltips copied verbatim from extensions/dashboard-server/html.ts */\nconst TOOLTIPS = {\n\tgameMode: \"Turn game mode on/off (themes the widget + dashboard)\",\n\ttheme: \"Visual theme (applies instantly)\",\n\ttuiMode: \"TUI widget display density\",\n\ttier: \"Live pressure band — climbs low\\u2192mega as context fills the window.\",\n\tpreset:\n\t\t\"The env-resolved base compaction preset (low/medium/high/ultra/mega) that set the token threshold.\",\n\tpressure:\n\t\t\"Live pressure = currentTokens / threshold \\u2014 % of the model context window (threshold fires at the tier's % of window).\",\n\tthreshold:\n\t\t\"Compaction threshold = tierPct \\u00d7 model context window \\u2014 mega-compact trims BELOW pi's native ~80% auto-compact for any model size.\",\n\tfastGate:\n\t\t\"Fast-gate arming floor \\u2014 the live trim arms once context passes this % of the window.\",\n} as const;\n\nfunction formatThreshold(\n\ttokens: number,\n\ttierPct: number | null | undefined,\n\tcw: number,\n): string {\n\tlet txt = tokens.toLocaleString();\n\tif (tierPct != null && cw > 0) {\n\t\ttxt += ` (${Math.round(tierPct * 100)}% of ${cw.toLocaleString()})`;\n\t}\n\treturn txt;\n}\n\nfunction formatPressure(pressure: number): string {\n\t/* Runtime sends 0\\u20131 fraction despite contract saying 0\\u2013100. */\n\treturn `${Math.round((pressure || 0) * 100)}%`;\n}\n\nexport default function ConfigTab(): React.ReactElement {\n\tconst [snapshot, setSnapshot] = useState<SnapshotResponse | null>(null);\n\tconst [gameState, setGameState] = useState<FlatGameState | null>(null);\n\tconst [saving, setSaving] = useState(false);\n\n\t/* Poll snapshot every 5s for config display section. */\n\tuseEffect(() => {\n\t\tlet active = true;\n\t\tconst doFetch = async (): Promise<void> => {\n\t\t\ttry {\n\t\t\t\tconst s = await fetchSnapshot();\n\t\t\t\tif (active) setSnapshot(s);\n\t\t\t} catch {\n\t\t\t\t/* non-fatal */\n\t\t\t}\n\t\t};\n\t\tvoid doFetch();\n\t\tconst timer = setInterval(() => void doFetch(), 5000);\n\t\treturn () => {\n\t\t\tactive = false;\n\t\t\tclearInterval(timer);\n\t\t};\n\t}, []);\n\n\t/* Poll game state every 5s. */\n\tuseEffect(() => {\n\t\tlet active = true;\n\t\tconst doFetch = async (): Promise<void> => {\n\t\t\ttry {\n\t\t\t\tconst gs = (await fetchGameState()) as unknown as FlatGameState;\n\t\t\t\tif (active) {\n\t\t\t\t\tsetGameState(gs);\n\t\t\t\t\tif (gs.theme)\n\t\t\t\t\t\tdocument.documentElement.dataset.theme = gs.theme;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t/* non-fatal */\n\t\t\t}\n\t\t};\n\t\tvoid doFetch();\n\t\tconst timer = setInterval(() => void doFetch(), 5000);\n\t\treturn () => {\n\t\t\tactive = false;\n\t\t\tclearInterval(timer);\n\t\t};\n\t}, []);\n\n\t/* PUT a patch to the server immediately on change. */\n\tconst handlePatch = useCallback(async (patch: FlatGameStatePatch) => {\n\t\tsetSaving(true);\n\t\ttry {\n\t\t\tconst result = (await putGameState(\n\t\t\t\tpatch as unknown as GameStatePatch,\n\t\t\t)) as unknown as FlatGameState;\n\t\t\tsetGameState(result);\n\t\t\tif (result.theme)\n\t\t\t\tdocument.documentElement.dataset.theme = result.theme;\n\t\t} catch {\n\t\t\t/* non-fatal */\n\t\t}\n\t\tsetSaving(false);\n\t}, []);\n\n\tconst gmOn = gameState?.game_mode_on ?? false;\n\tconst themeVal = gameState?.theme ?? \"transparent\";\n\tconst tuiVal = gameState?.tui_display_mode ?? \"full\";\n\n\tconst cfg = snapshot?.config as ConfigExt | undefined;\n\tconst cw = snapshot?.context.contextWindow ?? 0;\n\n\treturn (\n\t\t<div className=\"config-tab\">\n\t\t\t<section className=\"config-settings\">\n\t\t\t\t<h3>Settings</h3>\n\t\t\t\t<div className=\"settings-row\">\n\t\t\t\t\t<label title={TOOLTIPS.gameMode}>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tchecked={gmOn}\n\t\t\t\t\t\t\tonChange={(e) =>\n\t\t\t\t\t\t\t\thandlePatch({ game_mode_on: e.currentTarget.checked })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdisabled={saving}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\tGame Mode\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"settings-row\">\n\t\t\t\t\t<label title={TOOLTIPS.theme}>\n\t\t\t\t\t\tTheme\n\t\t\t\t\t\t<select\n\t\t\t\t\t\t\tvalue={themeVal}\n\t\t\t\t\t\t\tonChange={(e) => handlePatch({ theme: e.currentTarget.value })}\n\t\t\t\t\t\t\tdisabled={saving}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{THEMES.map((t) => (\n\t\t\t\t\t\t\t\t<option key={t.id} value={t.id}>\n\t\t\t\t\t\t\t\t\t{t.label}\n\t\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</select>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tdisabled={saving}\n\t\t\t\t\t\t\tclassName=\"theme-cycle-btn\"\n\t\t\t\t\t\t\ttitle=\"Cycle to the next theme (P3)\"\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\tconst i = THEME_IDS.indexOf(themeVal);\n\t\t\t\t\t\t\t\tconst next =\n\t\t\t\t\t\t\t\t\tTHEME_IDS[(i < 0 ? 0 : i + 1) % THEME_IDS.length];\n\t\t\t\t\t\t\t\tif (next) void handlePatch({ theme: next });\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tNext theme →\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"settings-row\">\n\t\t\t\t\t<label title={TOOLTIPS.tuiMode}>\n\t\t\t\t\t\tTUI Mode\n\t\t\t\t\t\t<select\n\t\t\t\t\t\t\tvalue={tuiVal}\n\t\t\t\t\t\t\tonChange={(e) =>\n\t\t\t\t\t\t\t\thandlePatch({\n\t\t\t\t\t\t\t\t\ttui_display_mode: e.currentTarget.value as \"full\" | \"minimal\",\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdisabled={saving}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<option value=\"full\">Full</option>\n\t\t\t\t\t\t\t<option value=\"minimal\">Minimal</option>\n\t\t\t\t\t\t</select>{\" \"}\n\t\t\t\t\t\t\t<span className=\"tui-mode-hint\">\n\t\t\t\t\t\t\t\t(affects the in-app TUI widget; not shown here)\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t</section>\n\n\t\t\t<section className=\"config-display\">\n\t\t\t\t<h3>Configuration</h3>\n\t\t\t\t<div className=\"config-grid\">\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\" title={TOOLTIPS.tier}>\n\t\t\t\t\t\t\tTier\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{snapshot ? `${snapshot.tier} (live)` : \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\" title={TOOLTIPS.preset}>\n\t\t\t\t\t\t\tPreset\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{snapshot?.presetTier ?? \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\" title={TOOLTIPS.pressure}>\n\t\t\t\t\t\t\tPressure\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{snapshot ? formatPressure(snapshot.pressure) : \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\" title={TOOLTIPS.threshold}>\n\t\t\t\t\t\t\tThreshold\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{cfg ? formatThreshold(cfg.thresholdTokens, cfg.tierPct, cw) : \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\" title={TOOLTIPS.fastGate}>\n\t\t\t\t\t\t\tFast Gate\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{cfg ? `${cfg.fastGatePct}%` : \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\">Auto</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{cfg ? (cfg.auto ? \"enabled\" : \"disabled\") : \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\">Anchor</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{cfg?.anchorUserMessages ?? \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</section>\n\t\t</div>\n\t);\n}\n"],"names":["THEMES","THEME_IDS","TOOLTIPS","formatThreshold","tokens","tierPct","cw","txt","formatPressure","pressure","ConfigTab","snapshot","setSnapshot","useState","gameState","setGameState","saving","setSaving","useEffect","active","doFetch","s","fetchSnapshot","timer","gs","fetchGameState","handlePatch","useCallback","patch","result","putGameState","gmOn","themeVal","tuiVal","cfg","jsxs","jsx","e","t","i","next"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ConfigTab-DG0WkuuG.js","sources":["../../../../src/config/themes.ts","../../src/tabs/ConfigTab.tsx"],"sourcesContent":["/**\n * themes.ts — SINGLE SOURCE OF TRUTH for game-mode visual-effect themes (S30).\n *\n * Each theme defines BOTH an HTML/CSS palette (dashboard) AND an ANSI accent\n * mapping (TUI widget), since the TUI is ANSI and the dashboard is HTML.\n *\n * Theme scope (QA1 correction): themes restyle the mega-compact statusbar\n * widget + the whole dashboard HTML page. We do NOT theme pi's chrome.\n *\n * `transparent` (default) semantics: no background fill, colored accents only —\n * so game-mode stays visible against any terminal background.\n *\n * PREVENT-PI-004: pure config, no network. Pi-agnostic: no pi runtime types.\n */\n\n/** HTML/CSS palette for the dashboard (hex strings, or `null` for \"no bg\"). */\nexport interface ThemeCss {\n /** Page/cell background hex, or `null` for transparent (no bg fill). */\n bg: string | null;\n /** Default foreground (text) hex. */\n fg: string;\n /** Accent hex (bars, highlights, level numbers). */\n accent: string;\n /** MEGA CACHE highlight hex (fires at cache >= 100%). */\n mega: string;\n}\n\n/** ANSI accent mapping for the TUI widget (SGR parameter strings, no `\\x1b[`).\n * `bg` is `null` for transparent themes (no background fill). */\nexport interface ThemeAnsi {\n /** Background SGR params (e.g. \"48;5;23\"), or `null` for no bg fill. */\n bg: string | null;\n /** Foreground SGR params (e.g. \"39\" for default, \"92\" for bright green). */\n fg: string;\n /** Accent SGR params. */\n accent: string;\n /** MEGA CACHE SGR params (fires at cache >= 100%). */\n mega: string;\n}\n\n/** A complete theme = CSS palette + ANSI mapping + display metadata. */\nexport interface Theme {\n id: string;\n label: string;\n css: ThemeCss;\n ansi: ThemeAnsi;\n}\n\nexport const DEFAULT_THEME = \"transparent\";\n\nexport const THEMES: readonly Theme[] = [\n {\n id: \"transparent\",\n label: \"Transparent (accents only)\",\n css: { bg: null, fg: \"#c9d1d9\", accent: \"#3fb950\", mega: \"#f0883e\" },\n ansi: { bg: null, fg: \"39\", accent: \"32\", mega: \"33\" },\n },\n {\n id: \"retro\",\n label: \"Retro 8-bit green\",\n css: { bg: \"#003300\", fg: \"#33ff33\", accent: \"#00ff41\", mega: \"#39ff14\" },\n ansi: { bg: \"40\", fg: \"92\", accent: \"92\", mega: \"1;92\" },\n },\n {\n id: \"orange-bold\",\n label: \"Orange bold\",\n css: { bg: \"#1a0f00\", fg: \"#ff8c00\", accent: \"#ff6b00\", mega: \"#ff4500\" },\n ansi: { bg: \"48;5;52\", fg: \"38;5;208\", accent: \"38;5;208\", mega: \"1;38;5;202\" },\n },\n {\n id: \"cyan-neon\",\n label: \"Cyan neon\",\n css: { bg: \"#001a1a\", fg: \"#00ffff\", accent: \"#22d3ee\", mega: \"#7df9ff\" },\n ansi: { bg: \"48;5;23\", fg: \"38;5;51\", accent: \"38;5;51\", mega: \"1;38;5;51\" },\n },\n {\n id: \"amber-mono\",\n label: \"Amber phosphor\",\n css: { bg: \"#1a1200\", fg: \"#ffb000\", accent: \"#ff8c00\", mega: \"#ff5500\" },\n ansi: { bg: \"48;5;58\", fg: \"38;5;214\", accent: \"38;5;214\", mega: \"1;38;5;208\" },\n },\n {\n id: \"grayscale\",\n label: \"Grayscale minimal\",\n css: { bg: \"#161616\", fg: \"#b0b0b0\", accent: \"#8b949e\", mega: \"#e6edf3\" },\n ansi: { bg: \"48;5;235\", fg: \"38;5;249\", accent: \"38;5;249\", mega: \"1;38;5;255\" },\n },\n] as const;\n\n/** All valid theme ids, in definition order (used for `theme next` cycling). */\nexport const THEME_IDS: readonly string[] = THEMES.map((t) => t.id);\n\n/** Look up a theme by id; returns `undefined` for an unknown id (caller\n * validates + falls back to DEFAULT_THEME). */\nexport function getTheme(id: string): Theme | undefined {\n return THEMES.find((t) => t.id === id);\n}\n\n/** True if `id` is a known theme id. */\nexport function isValidTheme(id: string): boolean {\n return THEME_IDS.includes(id);\n}\n\n/** Cycle to the next theme id (wraps at the end). Falls back to DEFAULT_THEME\n * if `current` is unknown (defensive — the stored id should always be valid). */\nexport function nextTheme(current: string): string {\n const i = THEME_IDS.indexOf(current);\n if (i < 0) return DEFAULT_THEME;\n return THEME_IDS[(i + 1) % THEME_IDS.length]!;\n}\n\n/** CSS variable declarations for a theme's CSS palette. `bg` is `null` for the\n * transparent theme → `--bg: transparent` (no page fill, accents only). Pure,\n * no pi types. */\nexport function themeCssVars(theme: Theme): string {\n const bg = theme.css.bg ?? \"transparent\";\n return `--bg: ${bg}; --fg: ${theme.css.fg}; --accent: ${theme.css.accent}; --mega: ${theme.css.mega};`;\n}\n\n/** A `:root[data-theme=\"<id>\"]` override block carrying the theme's CSS vars,\n * for client-side instant theme switching. Pure, no pi types. */\nexport function themeDataBlock(theme: Theme): string {\n return `:root[data-theme=\"${theme.id}\"]{ ${themeCssVars(theme)} }`;\n}\n","/**\n * dashboard-client/src/tabs/ConfigTab.tsx — Config tab (C3).\n *\n * Full configuration editor:\n * (1) Game mode toggle (checkbox)\n * (2) Theme selector (dropdown) from src/config/themes.ts THEMES array\n * (3) TUI mode selector (Full/Minimal dropdown)\n * (4) Read-only config display section from snapshot (Tier, Preset, Pressure,\n * Threshold, Fast Gate, Auto, Anchor) with verbatim tooltips from html.ts.\n *\n * Uses fetchGameState() to load current state, putGameState() to save changes.\n * Polls gameState and snapshot every 5s. Applies theme by setting\n * document.documentElement.dataset.theme.\n */\n\nimport type React from \"react\";\nimport { useState, useEffect, useCallback } from \"react\";\nimport type { SnapshotResponse, GameStatePatch } from \"@contracts\";\nimport { fetchSnapshot, fetchGameState, putGameState } from \"../api/client\";\nimport { THEMES, THEME_IDS } from \"../../../../src/config/themes\";\n\n/** Actual flat game state returned by the server (the server returns a flat\n * { game_mode_on, theme, tui_display_mode } shape, NOT the contract's nested\n * { config: { enabled, theme, displayMode }, activeRitual } shape). */\ninterface FlatGameState {\n\tgame_mode_on: boolean;\n\ttheme: string;\n\ttui_display_mode: \"full\" | \"minimal\";\n}\ntype FlatGameStatePatch = Partial<FlatGameState>;\n\n/** Extended snapshot config — the runtime includes tierPct (not in contract).\n * Implemented as a type intersection rather than `interface extends` because\n * TypeScript (TS2499) forbids `interface extends T[\"k\"]` indexed-access\n * parents; the resulting type is structurally identical. */\ntype ConfigExt = SnapshotResponse[\"config\"] & {\n\ttierPct?: number | null;\n};\n\n/* Tooltips copied verbatim from extensions/dashboard-server/html.ts */\nconst TOOLTIPS = {\n\tgameMode: \"Turn game mode on/off (themes the widget + dashboard)\",\n\ttheme: \"Visual theme (applies instantly)\",\n\ttuiMode: \"TUI widget display density\",\n\ttier: \"Live pressure band — climbs low\\u2192mega as context fills the window.\",\n\tpreset:\n\t\t\"The env-resolved base compaction preset (low/medium/high/ultra/mega) that set the token threshold.\",\n\tpressure:\n\t\t\"Live pressure = currentTokens / threshold \\u2014 % of the model context window (threshold fires at the tier's % of window).\",\n\tthreshold:\n\t\t\"Compaction threshold = tierPct \\u00d7 model context window \\u2014 mega-compact trims BELOW pi's native ~80% auto-compact for any model size.\",\n\tfastGate:\n\t\t\"Fast-gate arming floor \\u2014 the live trim arms once context passes this % of the window.\",\n} as const;\n\nfunction formatThreshold(\n\ttokens: number,\n\ttierPct: number | null | undefined,\n\tcw: number,\n): string {\n\tlet txt = tokens.toLocaleString();\n\tif (tierPct != null && cw > 0) {\n\t\ttxt += ` (${Math.round(tierPct * 100)}% of ${cw.toLocaleString()})`;\n\t}\n\treturn txt;\n}\n\nfunction formatPressure(pressure: number): string {\n\t/* Runtime sends 0\\u20131 fraction despite contract saying 0\\u2013100. */\n\treturn `${Math.round((pressure || 0) * 100)}%`;\n}\n\nexport default function ConfigTab(): React.ReactElement {\n\tconst [snapshot, setSnapshot] = useState<SnapshotResponse | null>(null);\n\tconst [gameState, setGameState] = useState<FlatGameState | null>(null);\n\tconst [saving, setSaving] = useState(false);\n\n\t/* Poll snapshot every 5s for config display section. */\n\tuseEffect(() => {\n\t\tlet active = true;\n\t\tconst doFetch = async (): Promise<void> => {\n\t\t\ttry {\n\t\t\t\tconst s = await fetchSnapshot();\n\t\t\t\tif (active) setSnapshot(s);\n\t\t\t} catch {\n\t\t\t\t/* non-fatal */\n\t\t\t}\n\t\t};\n\t\tvoid doFetch();\n\t\tconst timer = setInterval(() => void doFetch(), 5000);\n\t\treturn () => {\n\t\t\tactive = false;\n\t\t\tclearInterval(timer);\n\t\t};\n\t}, []);\n\n\t/* Poll game state every 5s. */\n\tuseEffect(() => {\n\t\tlet active = true;\n\t\tconst doFetch = async (): Promise<void> => {\n\t\t\ttry {\n\t\t\t\tconst gs = (await fetchGameState()) as unknown as FlatGameState;\n\t\t\t\tif (active) {\n\t\t\t\t\tsetGameState(gs);\n\t\t\t\t\tif (gs.theme)\n\t\t\t\t\t\tdocument.documentElement.dataset.theme = gs.theme;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t/* non-fatal */\n\t\t\t}\n\t\t};\n\t\tvoid doFetch();\n\t\tconst timer = setInterval(() => void doFetch(), 5000);\n\t\treturn () => {\n\t\t\tactive = false;\n\t\t\tclearInterval(timer);\n\t\t};\n\t}, []);\n\n\t/* PUT a patch to the server immediately on change. */\n\tconst handlePatch = useCallback(async (patch: FlatGameStatePatch) => {\n\t\tsetSaving(true);\n\t\ttry {\n\t\t\tconst result = (await putGameState(\n\t\t\t\tpatch as unknown as GameStatePatch,\n\t\t\t)) as unknown as FlatGameState;\n\t\t\tsetGameState(result);\n\t\t\tif (result.theme)\n\t\t\t\tdocument.documentElement.dataset.theme = result.theme;\n\t\t} catch {\n\t\t\t/* non-fatal */\n\t\t}\n\t\tsetSaving(false);\n\t}, []);\n\n\tconst gmOn = gameState?.game_mode_on ?? false;\n\tconst themeVal = gameState?.theme ?? \"transparent\";\n\tconst tuiVal = gameState?.tui_display_mode ?? \"full\";\n\n\tconst cfg = snapshot?.config as ConfigExt | undefined;\n\tconst cw = snapshot?.context.contextWindow ?? 0;\n\n\treturn (\n\t\t<div className=\"config-tab\">\n\t\t\t<section className=\"config-settings\">\n\t\t\t\t<h3>Settings</h3>\n\t\t\t\t<div className=\"settings-row\">\n\t\t\t\t\t<label title={TOOLTIPS.gameMode}>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tchecked={gmOn}\n\t\t\t\t\t\t\tonChange={(e) =>\n\t\t\t\t\t\t\t\thandlePatch({ game_mode_on: e.currentTarget.checked })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdisabled={saving}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\tGame Mode\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"settings-row\">\n\t\t\t\t\t<label title={TOOLTIPS.theme}>\n\t\t\t\t\t\tTheme\n\t\t\t\t\t\t<select\n\t\t\t\t\t\t\tvalue={themeVal}\n\t\t\t\t\t\t\tonChange={(e) => handlePatch({ theme: e.currentTarget.value })}\n\t\t\t\t\t\t\tdisabled={saving}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{THEMES.map((t) => (\n\t\t\t\t\t\t\t\t<option key={t.id} value={t.id}>\n\t\t\t\t\t\t\t\t\t{t.label}\n\t\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</select>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tdisabled={saving}\n\t\t\t\t\t\t\tclassName=\"theme-cycle-btn\"\n\t\t\t\t\t\t\ttitle=\"Cycle to the next theme (P3)\"\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\tconst i = THEME_IDS.indexOf(themeVal);\n\t\t\t\t\t\t\t\tconst next =\n\t\t\t\t\t\t\t\t\tTHEME_IDS[(i < 0 ? 0 : i + 1) % THEME_IDS.length];\n\t\t\t\t\t\t\t\tif (next) void handlePatch({ theme: next });\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tNext theme →\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"settings-row\">\n\t\t\t\t\t<label title={TOOLTIPS.tuiMode}>\n\t\t\t\t\t\tTUI Mode\n\t\t\t\t\t\t<select\n\t\t\t\t\t\t\tvalue={tuiVal}\n\t\t\t\t\t\t\tonChange={(e) =>\n\t\t\t\t\t\t\t\thandlePatch({\n\t\t\t\t\t\t\t\t\ttui_display_mode: e.currentTarget.value as \"full\" | \"minimal\",\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdisabled={saving}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<option value=\"full\">Full</option>\n\t\t\t\t\t\t\t<option value=\"minimal\">Minimal</option>\n\t\t\t\t\t\t</select>{\" \"}\n\t\t\t\t\t\t\t<span className=\"tui-mode-hint\">\n\t\t\t\t\t\t\t\t(affects the in-app TUI widget; not shown here)\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t</section>\n\n\t\t\t<section className=\"config-display\">\n\t\t\t\t<h3>Configuration</h3>\n\t\t\t\t<div className=\"config-grid\">\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\" title={TOOLTIPS.tier}>\n\t\t\t\t\t\t\tTier\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{snapshot ? `${snapshot.tier} (live)` : \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\" title={TOOLTIPS.preset}>\n\t\t\t\t\t\t\tPreset\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{snapshot?.presetTier ?? \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\" title={TOOLTIPS.pressure}>\n\t\t\t\t\t\t\tPressure\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{snapshot ? formatPressure(snapshot.pressure) : \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\" title={TOOLTIPS.threshold}>\n\t\t\t\t\t\t\tThreshold\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{cfg ? formatThreshold(cfg.thresholdTokens, cfg.tierPct, cw) : \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\" title={TOOLTIPS.fastGate}>\n\t\t\t\t\t\t\tFast Gate\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{cfg ? `${cfg.fastGatePct}%` : \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\">Auto</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{cfg ? (cfg.auto ? \"enabled\" : \"disabled\") : \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\">Anchor</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{cfg?.anchorUserMessages ?? \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</section>\n\t\t</div>\n\t);\n}\n"],"names":["THEMES","THEME_IDS","TOOLTIPS","formatThreshold","tokens","tierPct","cw","txt","formatPressure","pressure","ConfigTab","snapshot","setSnapshot","useState","gameState","setGameState","saving","setSaving","useEffect","active","doFetch","s","fetchSnapshot","timer","gs","fetchGameState","handlePatch","useCallback","patch","result","putGameState","gmOn","themeVal","tuiVal","cfg","jsxs","jsx","e","t","i","next"],"mappings":"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"}
|
package/extensions/dashboard-client/dist/assets/{EventsTab-DHytZ0xs.js → EventsTab-CzF5L42U.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-D-lPLRWB.js";import{u}from"./useSSE-ClbbcgIp.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-CzF5L42U.js.map
|