← Files Cache StatsARCHIVED FILE
skills/cache-stats/assets/runtime/ui/cache-card.html
12.3 KB · Oct 3, 2026 · 06:31 UTC
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
:root {
color-scheme: light dark;
--accent: #10b981;
--accent-strong: #059669;
--accent-soft: rgba(16, 185, 129, 0.13);
--surface: rgba(255, 255, 255, 0.94);
--surface-raised: rgba(248, 250, 252, 0.92);
--text: #10231d;
--muted: #5f6f69;
--border: rgba(16, 35, 29, 0.12);
--track: rgba(16, 35, 29, 0.09);
--shadow: 0 18px 50px rgba(15, 23, 42, 0.1);
}
@media (prefers-color-scheme: dark) {
:root {
--surface: rgba(18, 26, 23, 0.96);
--surface-raised: rgba(28, 39, 35, 0.92);
--text: #eefbf6;
--muted: #a5b7b0;
--border: rgba(226, 247, 239, 0.12);
--track: rgba(226, 247, 239, 0.1);
--shadow: 0 18px 50px rgba(0, 0, 0, 0.26);
}
}
* { box-sizing: border-box; }
body {
margin: 0;
padding: 2px;
background: transparent;
color: var(--text);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
.card {
position: relative;
overflow: hidden;
max-width: 720px;
border: 1px solid var(--border);
border-radius: 22px;
padding: 22px;
background:
radial-gradient(circle at 100% 0%, var(--accent-soft), transparent 38%),
var(--surface);
box-shadow: var(--shadow);
}
.card::before {
content: "";
position: absolute;
inset: 0 auto 0 0;
width: 4px;
background: linear-gradient(180deg, #34d399, var(--accent-strong));
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 22px;
}
.identity { display: flex; align-items: center; gap: 12px; min-width: 0; }
.mark {
display: grid;
width: 40px;
height: 40px;
flex: 0 0 auto;
place-items: center;
border-radius: 13px;
color: white;
background: linear-gradient(145deg, #34d399, #059669);
box-shadow: 0 9px 22px rgba(5, 150, 105, 0.24);
}
.mark svg { width: 23px; height: 23px; }
.eyebrow { margin: 0 0 2px; color: var(--muted); font-size: 12px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; }
h1 { margin: 0; font-size: 20px; line-height: 1.2; letter-spacing: -0.025em; }
.status {
display: inline-flex;
align-items: center;
gap: 7px;
flex: 0 0 auto;
border: 1px solid rgba(16, 185, 129, 0.22);
border-radius: 999px;
padding: 7px 10px;
color: var(--accent-strong);
background: var(--accent-soft);
font-size: 12px;
font-weight: 750;
}
.status-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.12); }
.hero {
display: grid;
grid-template-columns: 132px minmax(0, 1fr);
align-items: center;
gap: 24px;
margin-bottom: 20px;
}
.ring {
--pct: 0;
position: relative;
width: 124px;
height: 124px;
border-radius: 50%;
background: conic-gradient(var(--accent) calc(var(--pct) * 1%), var(--track) 0);
}
.ring::after {
content: "";
position: absolute;
inset: 12px;
border: 1px solid var(--border);
border-radius: inherit;
background: var(--surface);
}
.ring-copy {
position: absolute;
z-index: 1;
inset: 0;
display: grid;
place-content: center;
text-align: center;
}
.percent { font-size: 28px; font-weight: 800; line-height: 1; letter-spacing: -0.055em; }
.percent-label { margin-top: 5px; color: var(--muted); font-size: 11px; font-weight: 650; }
.summary { margin: 0 0 14px; font-size: 17px; font-weight: 650; line-height: 1.45; letter-spacing: -0.015em; }
.bar { overflow: hidden; height: 8px; border-radius: 999px; background: var(--track); }
.bar-fill { width: 0; height: 100%; border-radius: inherit; background: linear-gradient(90deg, #34d399, #059669); transition: width 520ms cubic-bezier(.2,.8,.2,1); }
.bar-legend { display: flex; justify-content: space-between; gap: 12px; margin-top: 8px; color: var(--muted); font-size: 11px; }
.metrics { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.metric { min-width: 0; border: 1px solid var(--border); border-radius: 15px; padding: 13px; background: var(--surface-raised); }
.metric-label { margin-bottom: 7px; color: var(--muted); font-size: 11px; font-weight: 700; letter-spacing: 0.025em; text-transform: uppercase; }
.metric-value { overflow: hidden; font-size: 19px; font-weight: 780; letter-spacing: -0.035em; text-overflow: ellipsis; white-space: nowrap; }
.metric.cached .metric-value { color: var(--accent-strong); }
.metric.write .metric-value { color: #2563eb; }
.footer { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-top: 16px; padding-top: 15px; border-top: 1px solid var(--border); }
.context { overflow: hidden; color: var(--muted); font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }
.note { margin: 0; max-width: 290px; color: var(--muted); font-size: 11px; line-height: 1.45; text-align: right; }
.loading { color: var(--muted); }
.error .status { color: #dc2626; border-color: rgba(220, 38, 38, 0.2); background: rgba(220, 38, 38, 0.08); }
.error .status-dot { box-shadow: 0 0 0 4px rgba(220, 38, 38, 0.1); }
@media (max-width: 520px) {
.card { border-radius: 18px; padding: 18px; }
.header { align-items: flex-start; }
.hero { grid-template-columns: 1fr; justify-items: center; text-align: center; }
.hero-copy { width: 100%; }
.metrics { grid-template-columns: 1fr; }
.footer { align-items: flex-start; flex-direction: column; }
.note { max-width: none; text-align: left; }
}
</style>
</head>
<body>
<main class="card loading" id="card" aria-live="polite">
<header class="header">
<div class="identity">
<div class="mark" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none">
<path d="M4 15a8 8 0 1 1 16 0" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
<path d="m12 15 4-5" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
<circle cx="12" cy="15" r="2" fill="currentColor" />
</svg>
</div>
<div>
<p class="eyebrow">Previous response</p>
<h1>Prompt cache</h1>
</div>
</div>
<div class="status"><span class="status-dot"></span><span id="statusText">Reading usage</span></div>
</header>
<section class="hero">
<div class="ring" id="ring" role="meter" aria-label="Cached input percentage" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
<div class="ring-copy"><span class="percent" id="percent">—</span><span class="percent-label">input reused</span></div>
</div>
<div class="hero-copy">
<p class="summary" id="summary">Reading the most recent completed response…</p>
<div class="bar" aria-hidden="true"><div class="bar-fill" id="barFill"></div></div>
<div class="bar-legend"><span>Cached input</span><span>Non-cached input</span></div>
</div>
</section>
<section class="metrics" aria-label="Token counts">
<article class="metric cached"><div class="metric-label">Cached input</div><div class="metric-value" id="cached">—</div></article>
<article class="metric write"><div class="metric-label">Cache write</div><div class="metric-value" id="cacheWrite">—</div></article>
<article class="metric"><div class="metric-label">Non-cached input</div><div class="metric-value" id="fresh">—</div></article>
<article class="metric"><div class="metric-label">Generated output</div><div class="metric-value" id="output">—</div></article>
</section>
<footer class="footer">
<div class="context" id="context">Waiting for usage data</div>
<p class="note">Cache writes are included in non-cached input. Reuse percentage measures tokens, not cost savings. Output is newly generated.</p>
</footer>
</main>
<script>
const card = document.getElementById("card");
const formatter = new Intl.NumberFormat();
function setText(id, value) {
document.getElementById(id).textContent = String(value);
}
function reasoningLabel(value) {
const labels = {
none: "None",
low: "Low",
medium: "Medium",
high: "High",
xhigh: "Extra High (xhigh)",
max: "Max",
};
const key = String(value || "").toLowerCase();
return labels[key] || (value ? String(value) : "Unspecified reasoning");
}
function render(payload) {
const stats = payload && payload.structuredContent ? payload.structuredContent : payload;
if (stats && stats.error) {
card.classList.remove("loading");
card.classList.add("error");
document.getElementById("ring").style.setProperty("--pct", "0");
document.getElementById("barFill").style.width = "0%";
setText("statusText", "Unable to verify task");
setText("percent", "—");
setText("summary", String(stats.error));
setText("cached", "—");
setText("cacheWrite", "—");
setText("fresh", "—");
setText("output", "—");
setText("context", "No cross-task usage was shown");
return;
}
if (!stats || typeof stats.cachePercent !== "number") return;
const percent = Math.max(0, Math.min(100, stats.cachePercent));
const hit = Boolean(stats.cacheHit);
const calls = Number(stats.modelCalls || 0);
const ring = document.getElementById("ring");
card.classList.remove("loading", "error");
ring.style.setProperty("--pct", String(percent));
ring.setAttribute("aria-valuenow", String(percent));
document.getElementById("barFill").style.width = percent + "%";
setText("statusText", hit ? "Cache hit" : "No cached input");
setText("percent", percent.toFixed(1) + "%");
setText(
"summary",
hit
? formatter.format(stats.cachedInputTokens) + " input tokens were reused from cache."
: "No input tokens were reused from cache.",
);
setText("cached", formatter.format(stats.cachedInputTokens));
setText("cacheWrite", formatter.format(stats.cacheWriteInputTokens || 0));
setText("fresh", formatter.format(stats.freshInputTokens));
setText("output", formatter.format(stats.outputTokens));
const context = document.getElementById("context");
const model = String(stats.model || "Unknown model");
const effort = reasoningLabel(stats.reasoningEffort);
const taskStatus = stats.taskId ? "Task verified" : "Task not verified";
context.textContent =
model +
" · " +
effort +
" · " +
calls +
" model call" +
(calls === 1 ? "" : "s") +
" · " +
taskStatus;
context.title =
model +
" · reasoning " +
String(stats.reasoningEffort || "unspecified") +
" · task " +
String(stats.taskId || "unverified");
}
const initialOutput = window.openai && window.openai.toolOutput;
if (initialOutput) render(initialOutput);
window.addEventListener(
"message",
(event) => {
if (event.source !== window.parent) return;
const message = event.data;
if (!message || message.jsonrpc !== "2.0") return;
if (message.method === "ui/notifications/tool-result") {
render(message.params && message.params.structuredContent);
}
},
{ passive: true },
);
</script>
</body>
</html>
SHA-256: 926545faa56d7100eed302f37adbcf52465882f5e5f9046f6a8ed3b18a5042b2