Skip to content

Instantly share code, notes, and snippets.

@sshh12
Created May 9, 2026 21:01
Show Gist options
  • Select an option

  • Save sshh12/d968c2c14da9ba554c7820c4a530ccc5 to your computer and use it in GitHub Desktop.

Select an option

Save sshh12/d968c2c14da9ba554c7820c4a530ccc5 to your computer and use it in GitHub Desktop.
Cache Ping Cost Analyzer
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Claude Cache Ping Analyzer</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;600&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #080808;
--s1: #111111;
--s2: #191919;
--border: #242424;
--text: #f0f0f0;
--muted: #909090;
--faint: #555;
--violet: #7c6aff;
--red: #f05050;
--amber: #e8a030;
--green: #3ecf74;
--blue: #60a5fa;
--font-sans: 'IBM Plex Sans', system-ui, sans-serif;
--font-mono: 'IBM Plex Mono', monospace;
}
html { font-size: 15px; }
body {
background: var(--bg);
color: var(--text);
font-family: var(--font-sans);
min-height: 100vh;
padding: 48px 24px 72px;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
/* ── Header ─────────────────────────────────────────────── */
header {
max-width: 960px;
margin: 0 auto 52px;
}
.header-eyebrow {
font-family: var(--font-sans);
font-size: 0.75rem;
font-weight: 500;
color: var(--muted);
letter-spacing: 0.04em;
text-transform: uppercase;
margin-bottom: 12px;
}
header h1 {
font-family: var(--font-sans);
font-size: clamp(1.6rem, 3.5vw, 2.4rem);
font-weight: 700;
color: var(--text);
letter-spacing: -0.02em;
line-height: 1.15;
margin-bottom: 12px;
}
header h1 em {
font-style: normal;
color: var(--violet);
}
header p {
color: var(--muted);
font-size: 0.9rem;
max-width: 480px;
line-height: 1.65;
}
/* ── Layout ─────────────────────────────────────────────── */
.layout {
display: grid;
grid-template-columns: 224px 1fr;
gap: 24px;
max-width: 1120px;
margin: 0 auto;
align-items: start;
}
@media (max-width: 760px) {
.layout { grid-template-columns: 1fr; }
.sidebar { position: static !important; }
}
/* ── Sidebar / Controls ──────────────────────────────────── */
.sidebar {
position: sticky;
top: 32px;
}
.ctrl-section {
margin-bottom: 32px;
}
.ctrl-section-label {
font-family: var(--font-sans);
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.07em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 16px;
}
.ctrl {
margin-bottom: 20px;
}
.ctrl:last-child { margin-bottom: 0; }
.ctrl-label {
display: flex;
justify-content: space-between;
align-items: baseline;
font-size: 0.82rem;
color: var(--muted);
margin-bottom: 8px;
}
.ctrl-val {
font-family: var(--font-mono);
font-size: 0.78rem;
color: var(--violet);
font-weight: 600;
}
input[type="range"] {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 2px;
background: var(--border);
border-radius: 2px;
outline: none;
cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 13px;
height: 13px;
border-radius: 50%;
background: var(--violet);
cursor: pointer;
transition: transform 0.15s;
}
input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.25); }
input[type="range"]::-moz-range-thumb {
width: 13px; height: 13px;
border-radius: 50%;
background: var(--violet);
border: none;
cursor: pointer;
}
select {
width: 100%;
background: transparent;
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text);
padding: 7px 10px;
font-size: 0.8rem;
font-family: var(--font-sans);
outline: none;
cursor: pointer;
-webkit-appearance: none;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23555' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 10px center;
padding-right: 28px;
}
select:focus { border-color: var(--violet); }
option { background: #1a1a1a; }
/* ── Main column ─────────────────────────────────────────── */
.main { display: flex; flex-direction: column; gap: 20px; }
/* ── Strategy cards ───────────────────────────────────────── */
.cards {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
@media (max-width: 700px) { .cards { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 420px) { .cards { grid-template-columns: 1fr; } }
.card {
background: var(--s1);
border: 1px solid var(--border);
border-radius: 10px;
padding: 18px 18px 16px;
position: relative;
overflow: hidden;
transition: border-color 0.25s;
}
.card::before {
content: '';
position: absolute;
inset: 0 auto 0 0;
width: 3px;
border-radius: 10px 0 0 10px;
background: transparent;
transition: background 0.25s;
}
.card.cheapest { border-color: var(--border); }
.card.cheapest::before { background: var(--green); }
.card.most-expensive::before { background: var(--red); opacity: 0.4; }
.card-meta {
font-family: var(--font-sans);
font-size: 0.68rem;
font-weight: 600;
color: var(--faint);
letter-spacing: 0.06em;
text-transform: uppercase;
margin-bottom: 4px;
}
.card-name {
font-family: var(--font-sans);
font-size: 0.9rem;
font-weight: 600;
color: var(--text);
margin-bottom: 16px;
}
.card-cost {
font-family: var(--font-mono);
font-size: 1.9rem;
font-weight: 700;
letter-spacing: -0.04em;
line-height: 1;
margin-bottom: 5px;
color: var(--text);
transition: color 0.25s;
}
.card.cheapest .card-cost { color: var(--green); }
.card.most-expensive .card-cost { color: var(--red); opacity: 0.8; }
.card-vs {
font-size: 0.78rem;
color: var(--muted);
margin-bottom: 14px;
min-height: 1em;
}
.card-vs .save { color: var(--green); }
.card-vs .loss { color: var(--red); }
.card-detail {
font-size: 0.78rem;
color: var(--muted);
line-height: 1.55;
padding-top: 12px;
border-top: 1px solid var(--border);
}
/* ── Chart panels ──────────────────────────────────────────── */
.panel {
background: var(--s1);
border: 1px solid var(--border);
border-radius: 10px;
padding: 22px 22px 20px;
}
.panel-header {
display: flex;
align-items: baseline;
gap: 12px;
margin-bottom: 20px;
}
.panel-title {
font-family: var(--font-sans);
font-size: 0.88rem;
font-weight: 600;
color: var(--text);
}
.panel-sub {
font-size: 0.78rem;
color: var(--muted);
}
.chart-wrap { position: relative; height: 240px; }
/* ── Footer ─────────────────────────────────────────────────── */
.footnote {
text-align: center;
font-family: var(--font-sans);
font-size: 0.72rem;
color: var(--faint);
margin-top: 32px;
max-width: 1120px;
margin-inline: auto;
line-height: 1.8;
}
.footnote a { color: var(--violet); text-decoration: none; }
.footnote a:hover { text-decoration: underline; }
</style>
</head>
<body>
<header>
<div class="header-eyebrow">Claude API · Prompt Caching</div>
<h1>Cache Ping<br><em>Cost Analyzer</em></h1>
<p>Claude caches repeated context (system prompts, docs, codebases) at 10% of normal input cost — but the cache expires after 5 min idle, forcing a 1.25× rewrite on the next call. For infrequent workflows, a cheap keep-alive ping resets the clock at that same 10% read rate. This tool shows when that's actually worth it.</p>
</header>
<div class="layout">
<!-- Sidebar -->
<div class="sidebar">
<div class="ctrl-section">
<div class="ctrl-section-label">Presets</div>
<div class="ctrl">
<select id="sel-preset">
<option value="">— custom —</option>
</select>
</div>
</div>
<div class="ctrl-section">
<div class="ctrl-section-label">Model</div>
<div class="ctrl">
<select id="sel-model">
<option value="opus">Opus 4.7</option>
<option value="sonnet">Sonnet 4.6</option>
<option value="haiku">Haiku 4.5</option>
</select>
</div>
</div>
<div class="ctrl-section">
<div class="ctrl-section-label">Context</div>
<div class="ctrl">
<div class="ctrl-label">
<span>Context tokens</span>
<span class="ctrl-val" id="lbl-ctx">500K</span>
</div>
<input type="range" id="sl-ctx" min="10000" max="800000" step="10000" value="500000">
</div>
<div class="ctrl">
<div class="ctrl-label">
<span>Output tokens / event</span>
<span class="ctrl-val" id="lbl-output">100</span>
</div>
<input type="range" id="sl-output" min="0" max="4000" step="100" value="100">
</div>
</div>
<div class="ctrl-section">
<div class="ctrl-section-label">Timing</div>
<div class="ctrl">
<div class="ctrl-label">
<span>Event interval</span>
<span class="ctrl-val"><span id="lbl-interval">8</span> min</span>
</div>
<input type="range" id="sl-interval" min="1" max="120" step="1" value="8">
</div>
<div class="ctrl">
<div class="ctrl-label">
<span>Ping interval</span>
<span class="ctrl-val"><span id="lbl-ping">4.5</span> min</span>
</div>
<input type="range" id="sl-ping" min="1" max="60" step="1" value="4">
</div>
<div class="ctrl">
<div class="ctrl-label"><span>Duration</span></div>
<select id="sel-duration">
<option value="1">1 hour</option>
<option value="4">4 hours</option>
<option value="8">8 hours</option>
<option value="24">24 hours</option>
<option value="168">7 days</option>
<option value="720">30 days</option>
<option value="8760">1 year</option>
</select>
</div>
</div>
</div>
<!-- Main -->
<div class="main">
<div class="cards">
<div class="card" id="card-a">
<div class="card-meta">Strategy A · 5-min TTL</div>
<div class="card-name">Default</div>
<div class="card-cost" id="cost-a">—</div>
<div class="card-vs" id="vs-a"></div>
<div class="card-detail" id="detail-a">Cache busts every event. Full rewrite each time.</div>
</div>
<div class="card" id="card-b">
<div class="card-meta">Strategy B · 1-hour TTL</div>
<div class="card-name">Extended TTL</div>
<div class="card-cost" id="cost-b">—</div>
<div class="card-vs" id="vs-b"></div>
<div class="card-detail" id="detail-b">Write at 2× rate; TTL extends 1 hour per read.</div>
</div>
<div class="card" id="card-c">
<div class="card-meta">Strategy C · 5-min TTL</div>
<div class="card-name">Keep-Alive Pings</div>
<div class="card-cost" id="cost-c">—</div>
<div class="card-vs" id="vs-c"></div>
<div class="card-detail" id="detail-c">Write at 1.25× rate; ping every 4.5 min to reset TTL.</div>
</div>
<div class="card" id="card-d">
<div class="card-meta">Strategy D · 1-hour TTL</div>
<div class="card-name">Extended + Pings</div>
<div class="card-cost" id="cost-d">—</div>
<div class="card-vs" id="vs-d"></div>
<div class="card-detail" id="detail-d">Write at 2× rate; ping at your interval to reset the 1-hour TTL.</div>
</div>
</div>
<div class="panel">
<div class="panel-header">
<span class="panel-title">Cumulative Cost</span>
<span class="panel-sub">over time</span>
</div>
<div class="chart-wrap"><canvas id="chart-time"></canvas></div>
</div>
<div class="panel">
<div class="panel-header">
<span class="panel-title">Break-Even Curve</span>
<span class="panel-sub">cost vs event interval, 1–120 min</span>
</div>
<div class="chart-wrap"><canvas id="chart-interval"></canvas></div>
</div>
</div>
</div>
<div class="footnote">
Opus 4.7 — $5/M input · $6.25/M 5m-write · $10/M 1h-write · $0.50/M read · $25/M output &nbsp;/&nbsp;
Sonnet 4.6 — $3 · $3.75 · $6 · $0.30 · $15 &nbsp;/&nbsp;
Haiku 4.5 — $1 · $1.25 · $2 · $0.10 · $5<br>
Source: <a href="https://platform.claude.com/docs/en/build-with-claude/prompt-caching">Anthropic prompt caching docs</a> · May 2026
</div>
<script>
const MODELS = {
opus: { label: 'Claude Opus 4.7', w5m: 6.25, w1h: 10, read: 0.50, out: 25 },
sonnet: { label: 'Claude Sonnet 4.6', w5m: 3.75, w1h: 6, read: 0.30, out: 15 },
haiku: { label: 'Claude Haiku 4.5', w5m: 1.25, w1h: 2, read: 0.10, out: 5 },
};
function simulate(p, ctxM, eventInterval, pingInterval, durationHours, outputM) {
const totalMin = durationHours * 60;
const eventTimes = [];
for (let t = eventInterval; t <= totalMin + 1e-9; t += eventInterval)
eventTimes.push(parseFloat(t.toFixed(4)));
// Strategy A
const tlA = [{ t: 0, c: 0 }];
let cA = 0;
if (eventInterval <= 5) {
cA += ctxM * p.w5m; tlA[0].c = cA;
for (const t of eventTimes) { cA += ctxM * p.read + outputM * p.out; tlA.push({ t, c: cA }); }
} else {
for (const t of eventTimes) { cA += ctxM * p.w5m + ctxM * p.read + outputM * p.out; tlA.push({ t, c: cA }); }
}
// Strategy B
const tlB = [{ t: 0, c: 0 }];
let cB = 0;
if (eventInterval <= 60) {
cB += ctxM * p.w1h; tlB[0].c = cB;
for (const t of eventTimes) { cB += ctxM * p.read + outputM * p.out; tlB.push({ t, c: cB }); }
} else {
for (const t of eventTimes) { cB += ctxM * p.w1h + ctxM * p.read + outputM * p.out; tlB.push({ t, c: cB }); }
}
// Strategy C: 5-min TTL + keep-alive pings
// If pingInterval >= 5, pings arrive after the cache has already expired — each
// access becomes a cache miss (write + read) just like Strategy A.
const TTL_C = 5;
const tlC = [{ t: 0, c: ctxM * p.w5m }];
let cC = ctxM * p.w5m;
let lastReadC = 0, numPings = 0;
for (const t of eventTimes) {
while (lastReadC + pingInterval < t - 1e-9) {
lastReadC += pingInterval; numPings++;
if (pingInterval >= TTL_C) cC += ctxM * p.w5m; // cache expired before ping
cC += ctxM * p.read;
tlC.push({ t: lastReadC, c: cC });
}
if (t - lastReadC >= TTL_C) cC += ctxM * p.w5m; // cache expired before event
cC += ctxM * p.read + outputM * p.out;
lastReadC = t; tlC.push({ t, c: cC });
}
// Strategy D: 1-hour TTL + keep-alive pings (uses same pingInterval as C)
// If pingInterval >= 60, pings arrive after the 1-hour TTL expires.
const TTL_D = 60;
const tlD = [{ t: 0, c: ctxM * p.w1h }];
let cD = ctxM * p.w1h;
let lastReadD = 0, numPingsD = 0;
for (const t of eventTimes) {
while (lastReadD + pingInterval < t - 1e-9) {
lastReadD += pingInterval; numPingsD++;
if (pingInterval >= TTL_D) cD += ctxM * p.w1h; // cache expired before ping
cD += ctxM * p.read;
tlD.push({ t: lastReadD, c: cD });
}
if (t - lastReadD >= TTL_D) cD += ctxM * p.w1h; // cache expired before event
cD += ctxM * p.read + outputM * p.out;
lastReadD = t; tlD.push({ t, c: cD });
}
return { costA: cA, costB: cB, costC: cC, costD: cD, tlA, tlB, tlC, tlD, numEvents: eventTimes.length, numPings, numPingsD };
}
function fmt(v) {
if (v === 0) return '$0.000';
if (v < 0.0001) return '$' + v.toExponential(2);
if (v < 0.01) return '$' + v.toFixed(5);
if (v < 1) return '$' + v.toFixed(4);
if (v < 100) return '$' + v.toFixed(3);
return '$' + v.toFixed(2);
}
function fmtK(v) {
if (v >= 1e6) return (v/1e6).toFixed(1) + 'M';
if (v >= 1e3) return Math.round(v/1e3) + 'K';
return v;
}
const CHART_DEFAULTS = (xLabel, yLabel) => ({
responsive: true,
maintainAspectRatio: false,
animation: { duration: 180 },
plugins: {
legend: {
labels: {
color: '#909090',
font: { size: 11, family: "'IBM Plex Sans', system-ui, sans-serif" },
boxWidth: 12, padding: 18,
usePointStyle: true, pointStyle: 'line',
}
},
tooltip: {
backgroundColor: '#111',
borderColor: '#2a2a2a',
borderWidth: 1,
titleColor: '#f0f0f0',
bodyColor: '#909090',
titleFont: { family: "'IBM Plex Sans', system-ui, sans-serif", size: 11 },
bodyFont: { family: "'IBM Plex Mono', monospace", size: 11 },
padding: 10,
callbacks: { label: ctx => ` ${ctx.dataset.label}: ${fmt(ctx.parsed.y)}` }
}
},
scales: {
x: {
ticks: { color: '#666', font: { size: 10, family: "'IBM Plex Sans', sans-serif" }, maxTicksLimit: 8 },
grid: { color: '#161616' },
border: { color: '#1e1e1e' },
title: { display: true, text: xLabel, color: '#666', font: { size: 11, family: "'IBM Plex Sans', sans-serif" } },
},
y: {
ticks: {
color: '#666', font: { size: 10, family: "'IBM Plex Mono', monospace" },
callback: v => fmt(v), maxTicksLimit: 6,
},
grid: { color: '#161616' },
border: { color: '#1e1e1e' },
title: { display: true, text: yLabel, color: '#666', font: { size: 11, family: "'IBM Plex Sans', sans-serif" } },
}
}
});
let chartTime, chartInterval;
function initCharts() {
chartTime = new Chart(document.getElementById('chart-time'), {
type: 'line', data: { datasets: [] },
options: CHART_DEFAULTS('Time (minutes)', 'Cumulative Cost ($)'),
});
chartInterval = new Chart(document.getElementById('chart-interval'), {
type: 'line', data: { datasets: [] },
options: CHART_DEFAULTS('Event Interval (minutes)', 'Total Cost ($)'),
});
}
function tlToPoints(tl, totalMin) {
const pts = [];
for (let i = 0; i <= 300; i++) {
const t = (i / 300) * totalMin;
let cost = 0;
for (let j = tl.length - 1; j >= 0; j--) {
if (tl[j].t <= t + 1e-9) { cost = tl[j].c; break; }
}
pts.push({ x: parseFloat(t.toFixed(2)), y: cost });
}
return pts;
}
function update() {
const p = MODELS[document.getElementById('sel-model').value];
const ctxTok = parseInt(document.getElementById('sl-ctx').value);
const eventInt = parseFloat(document.getElementById('sl-interval').value);
const pingInt = parseFloat(document.getElementById('sl-ping').value);
const durH = parseFloat(document.getElementById('sel-duration').value);
const outTok = parseInt(document.getElementById('sl-output').value);
const ctxM = ctxTok / 1e6, outM = outTok / 1e6, totalMin = durH * 60;
const { costA, costB, costC, costD, tlA, tlB, tlC, tlD, numEvents, numPings, numPingsD } =
simulate(p, ctxM, eventInt, pingInt, durH, outM);
const costs = [costA, costB, costC, costD];
const minC = Math.min(...costs), maxC = Math.max(...costs);
['a','b','c','d'].forEach((s, i) => {
document.getElementById(`cost-${s}`).textContent = fmt(costs[i]);
const card = document.getElementById(`card-${s}`);
card.classList.toggle('cheapest', costs[i] === minC);
card.classList.toggle('most-expensive', costs[i] === maxC && costs[i] !== minC);
});
const savB = costA > 0 ? (costA - costB) / costA * 100 : 0;
const savC = costA > 0 ? (costA - costC) / costA * 100 : 0;
const savD = costA > 0 ? (costA - costD) / costA * 100 : 0;
const vsHtml = (sav) => {
if (Math.abs(sav) < 0.5) return '';
return sav > 0
? `<span class="save">${sav.toFixed(0)}% less than Default</span>`
: `<span class="loss">${(-sav).toFixed(0)}% more than Default</span>`;
};
document.getElementById('vs-a').innerHTML = '';
document.getElementById('vs-b').innerHTML = vsHtml(savB);
document.getElementById('vs-c').innerHTML = vsHtml(savC);
document.getElementById('vs-d').innerHTML = vsHtml(savD);
const wpe = fmt(ctxM * p.w5m), rpa = fmt(ctxM * p.read);
document.getElementById('detail-a').textContent = eventInt <= 5
? `${numEvents} events — cache never busts (≤5 min TTL). Reads only.`
: `${numEvents} events × write ${wpe} + read ${rpa}.`;
document.getElementById('detail-b').textContent = eventInt <= 60
? `1 write at 2× + ${numEvents} reads over ${durH}h.`
: `${numEvents} writes at 2× — cache busts (>60 min TTL).`;
document.getElementById('detail-c').textContent =
`1 write · ${numPings} pings · ${numEvents} reads.`;
document.getElementById('detail-d').textContent =
`1 write at 2× · ${numPingsD} pings (${pingInt} min) · ${numEvents} reads.`;
// Time chart
chartTime.data.datasets = [
{ label: 'A · Default', data: tlToPoints(tlA, totalMin), borderColor: '#f05050', backgroundColor: 'rgba(240,80,80,0.04)', fill: true, tension: 0, pointRadius: 0, borderWidth: 1.5, stepped: 'before' },
{ label: 'B · Extended TTL', data: tlToPoints(tlB, totalMin), borderColor: '#e8a030', backgroundColor: 'rgba(232,160,48,0.04)', fill: true, tension: 0, pointRadius: 0, borderWidth: 1.5, stepped: 'before' },
{ label: 'C · Keep-Alive Pings', data: tlToPoints(tlC, totalMin), borderColor: '#3ecf74', backgroundColor: 'rgba(62,207,116,0.04)', fill: true, tension: 0, pointRadius: 0, borderWidth: 1.5, stepped: 'before' },
{ label: 'D · Extended + Pings', data: tlToPoints(tlD, totalMin), borderColor: '#60a5fa', backgroundColor: 'rgba(96,165,250,0.04)', fill: true, tension: 0, pointRadius: 0, borderWidth: 1.5, stepped: 'before' },
];
chartTime.options.scales.x.type = 'linear';
chartTime.update();
// Interval sweep
const ivLabels = [], swA = [], swB = [], swC = [], swD = [];
for (let iv = 1; iv <= 120; iv++) {
const r = simulate(p, ctxM, iv, pingInt, durH, outM);
ivLabels.push(iv); swA.push(r.costA); swB.push(r.costB); swC.push(r.costC); swD.push(r.costD);
}
chartInterval.data.labels = ivLabels;
chartInterval.data.datasets = [
{ label: 'A · Default', data: swA, borderColor: '#f05050', backgroundColor: 'transparent', tension: 0.3, pointRadius: 0, borderWidth: 1.5 },
{ label: 'B · Extended TTL', data: swB, borderColor: '#e8a030', backgroundColor: 'transparent', tension: 0.3, pointRadius: 0, borderWidth: 1.5 },
{ label: 'C · Keep-Alive Pings', data: swC, borderColor: '#3ecf74', backgroundColor: 'transparent', tension: 0.3, pointRadius: 0, borderWidth: 1.5 },
{ label: 'D · Extended + Pings', data: swD, borderColor: '#60a5fa', backgroundColor: 'transparent', tension: 0.3, pointRadius: 0, borderWidth: 1.5 },
];
chartInterval.update();
}
// ── Presets ───────────────────────────────────────────────────────────────────
const PRESETS = [
{
name: 'PR Review Bot',
sub: '300K ctx · PRs every 30 min · 8h day',
model: 'opus', ctx: 300000, eventInterval: 30, pingInterval: 4, duration: 8, output: 500,
},
{
name: 'Support Desk Agent',
sub: '100K knowledge base · ticket every 6 min',
model: 'sonnet', ctx: 100000, eventInterval: 6, pingInterval: 4, duration: 8, output: 250,
},
{
name: 'Legal Doc Q&A',
sub: '75K doc · analyst query every 12 min',
model: 'opus', ctx: 75000, eventInterval: 12, pingInterval: 4, duration: 4, output: 400,
},
{
name: 'Log / Alert Monitor',
sub: '50K runbook · alert every 2 min · 24h',
model: 'haiku', ctx: 50000, eventInterval: 2, pingInterval: 1.5, duration: 24, output: 100,
},
{
name: 'Research Agent',
sub: '400K papers + notes · step every 8 min',
model: 'opus', ctx: 400000, eventInterval: 8, pingInterval: 4, duration: 4, output: 800,
},
{
name: 'CI/CD Watchdog',
sub: '40K build config · build every 15 min',
model: 'haiku', ctx: 40000, eventInterval: 15, pingInterval: 4, duration: 8, output: 200,
},
{
name: 'Slack Bot',
sub: '20K system prompt · message every 3 min',
model: 'haiku', ctx: 20000, eventInterval: 3, pingInterval: 2.0, duration: 8, output: 150,
},
{
name: 'Overnight Pipeline',
sub: '80K pipeline defs · job every 20 min · 7d',
model: 'sonnet', ctx: 80000, eventInterval: 20, pingInterval: 4, duration: 168, output: 300,
},
];
// Populate preset select
const selPreset = document.getElementById('sel-preset');
PRESETS.forEach((preset, idx) => {
const opt = document.createElement('option');
opt.value = idx;
opt.textContent = preset.name;
selPreset.appendChild(opt);
});
function applyPreset(preset) {
document.getElementById('sel-model').value = preset.model;
document.getElementById('sl-ctx').value = preset.ctx;
document.getElementById('lbl-ctx').textContent = fmtK(preset.ctx);
document.getElementById('sl-interval').value = preset.eventInterval;
document.getElementById('lbl-interval').textContent = preset.eventInterval;
document.getElementById('sl-ping').value = preset.pingInterval;
document.getElementById('lbl-ping').textContent = preset.pingInterval;
document.getElementById('sel-duration').value = preset.duration;
document.getElementById('sl-output').value = preset.output;
document.getElementById('lbl-output').textContent = fmtK(preset.output);
update();
}
function clearPresetSelect() { selPreset.value = ''; }
selPreset.addEventListener('change', function() {
if (this.value !== '') applyPreset(PRESETS[parseInt(this.value)]);
});
document.getElementById('sl-ctx').addEventListener('input', function() { document.getElementById('lbl-ctx').textContent = fmtK(this.value); clearPresetSelect(); update(); });
document.getElementById('sl-interval').addEventListener('input', function() { document.getElementById('lbl-interval').textContent = this.value; clearPresetSelect(); update(); });
document.getElementById('sl-ping').addEventListener('input', function() { document.getElementById('lbl-ping').textContent = this.value; clearPresetSelect(); update(); });
document.getElementById('sl-output').addEventListener('input', function() { document.getElementById('lbl-output').textContent = fmtK(this.value); clearPresetSelect(); update(); });
document.getElementById('sel-model').addEventListener('change', () => { clearPresetSelect(); update(); });
document.getElementById('sel-duration').addEventListener('change', () => { clearPresetSelect(); update(); });
initCharts();
update();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment