Skip to content

Commit 34a06b2

Browse files
committed
Adding token calculator
1 parent 3f103e2 commit 34a06b2

1 file changed

Lines changed: 182 additions & 0 deletions

File tree

tools/token_calc.html

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Token Budget Explorer</title>
7+
<style>
8+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
9+
:root {
10+
--color-background-primary: #ffffff;
11+
--color-background-secondary: #f1efe8;
12+
--color-background-tertiary: #e8e6df;
13+
--color-text-primary: #1a1a18;
14+
--color-text-secondary: #5f5e5a;
15+
--color-text-tertiary: #888780;
16+
--color-border-tertiary: rgba(0,0,0,0.12);
17+
--color-border-secondary: rgba(0,0,0,0.22);
18+
--border-radius-md: 8px;
19+
--border-radius-lg: 12px;
20+
--font-sans: system-ui, -apple-system, sans-serif;
21+
}
22+
@media (prefers-color-scheme: dark) {
23+
:root {
24+
--color-background-primary: #1e1e1c;
25+
--color-background-secondary: #2a2a27;
26+
--color-background-tertiary: #333330;
27+
--color-text-primary: #e8e6df;
28+
--color-text-secondary: #b4b2a9;
29+
--color-text-tertiary: #888780;
30+
--color-border-tertiary: rgba(255,255,255,0.1);
31+
--color-border-secondary: rgba(255,255,255,0.18);
32+
}
33+
}
34+
body {
35+
font-family: var(--font-sans);
36+
background: var(--color-background-primary);
37+
color: var(--color-text-primary);
38+
padding: 2rem;
39+
max-width: 800px;
40+
margin: 0 auto;
41+
line-height: 1.5;
42+
}
43+
h1 { font-size: 18px; font-weight: 500; margin-bottom: 1.5rem; }
44+
.tok-section { margin-bottom: 1.5rem; }
45+
.tok-label { font-size: 13px; color: var(--color-text-secondary); margin-bottom: 6px; display: flex; justify-content: space-between; }
46+
.tok-name { font-weight: 500; color: var(--color-text-primary); }
47+
.tok-bar-wrap { height: 10px; background: var(--color-background-secondary); border-radius: 5px; overflow: hidden; margin-bottom: 4px; }
48+
.tok-bar { height: 100%; border-radius: 5px; transition: width 0.2s; }
49+
.metrics { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 10px; margin-bottom: 1.5rem; }
50+
.metric { background: var(--color-background-secondary); border-radius: var(--border-radius-md); padding: 12px; }
51+
.metric-label { font-size: 11px; color: var(--color-text-secondary); margin-bottom: 4px; }
52+
.metric-value { font-size: 20px; font-weight: 500; color: var(--color-text-primary); }
53+
.metric-sub { font-size: 11px; color: var(--color-text-tertiary); margin-top: 2px; }
54+
.window-viz { display: flex; height: 28px; border-radius: var(--border-radius-md); overflow: hidden; margin-bottom: 6px; border: 0.5px solid var(--color-border-tertiary); }
55+
.window-seg { transition: flex 0.2s; display: flex; align-items: center; justify-content: center; font-size: 10px; overflow: hidden; white-space: nowrap; }
56+
.legend { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 1.5rem; }
57+
.leg-item { display: flex; align-items: center; gap: 5px; font-size: 12px; color: var(--color-text-secondary); }
58+
.leg-dot { width: 10px; height: 10px; border-radius: 2px; flex-shrink: 0; }
59+
.slider-row { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
60+
.slider-row label { font-size: 13px; color: var(--color-text-secondary); min-width: 160px; }
61+
.slider-row input[type=range] { flex: 1; accent-color: #534AB7; }
62+
.slider-row span { font-size: 13px; font-weight: 500; min-width: 60px; text-align: right; color: var(--color-text-primary); }
63+
.cost-table { width: 100%; font-size: 13px; border-collapse: collapse; }
64+
.cost-table td { padding: 6px 8px; }
65+
.cost-table tr:not(:last-child) td { border-bottom: 0.5px solid var(--color-border-tertiary); }
66+
.cost-table .total td { font-weight: 500; }
67+
.section-head { font-size: 11px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-tertiary); margin-bottom: 10px; margin-top: 1.5rem; }
68+
</style>
69+
</head>
70+
<body>
71+
<h1>Token budget explorer — Azure AI Foundry</h1>
72+
73+
<div class="metrics" id="metrics"></div>
74+
<div class="window-viz" id="winviz"></div>
75+
<div class="legend" id="legend"></div>
76+
77+
<div class="section-head">Token types — adjust per call</div>
78+
<div id="sliders"></div>
79+
80+
<div class="section-head">Daily cost estimate</div>
81+
<table class="cost-table" id="costtable"></table>
82+
83+
<script>
84+
const COLORS = {
85+
system: '#534AB7',
86+
history: '#1D9E75',
87+
rag: '#378ADD',
88+
user: '#D85A30',
89+
output: '#BA7517',
90+
cache_in: '#888780',
91+
};
92+
93+
const TYPES = [
94+
{ id:'system', label:'System prompt', desc:'Instructions, persona, tools schema', def:800, min:100, max:4000, step:100, price_in:3.0, price_out:0 },
95+
{ id:'history', label:'Conversation history', desc:'Prior turns kept in context', def:1500, min:0, max:16000, step:500, price_in:3.0, price_out:0 },
96+
{ id:'rag', label:'RAG / grounding', desc:'Retrieved chunks injected per call', def:2000, min:0, max:12000, step:500, price_in:3.0, price_out:0 },
97+
{ id:'user', label:'User message', desc:'Current user turn', def:300, min:50, max:2000, step:50, price_in:3.0, price_out:0 },
98+
{ id:'output', label:'Model output', desc:'Generated completion tokens', def:600, min:50, max:4000, step:50, price_in:0, price_out:15.0},
99+
{ id:'cache_in', label:'Cached prompt (in)', desc:'Prompt cache read (50% discount)', def:600, min:0, max:8000, step:200, price_in:1.5, price_out:0 },
100+
];
101+
102+
const MODEL_CONTEXT = 128000;
103+
let vals = {};
104+
TYPES.forEach(t => vals[t.id] = t.def);
105+
let callsPerDay = 500;
106+
107+
function totalInput() { return TYPES.filter(t=>t.id!=='output').reduce((s,t)=>s+vals[t.id],0); }
108+
function totalTokens() { return totalInput() + vals['output']; }
109+
function dailyCost() {
110+
let cost = 0;
111+
TYPES.forEach(t => { cost += (vals[t.id]/1e6)*(t.price_out>0?t.price_out:t.price_in); });
112+
return cost * callsPerDay;
113+
}
114+
115+
function render() {
116+
const inp = totalInput();
117+
const tot = totalTokens();
118+
const ctxUsed = Math.min(tot, MODEL_CONTEXT);
119+
const cost = dailyCost();
120+
121+
document.getElementById('metrics').innerHTML = `
122+
<div class="metric"><div class="metric-label">Input tokens/call</div><div class="metric-value">${inp.toLocaleString()}</div><div class="metric-sub">excl. output</div></div>
123+
<div class="metric"><div class="metric-label">Output tokens/call</div><div class="metric-value">${vals['output'].toLocaleString()}</div><div class="metric-sub">completion</div></div>
124+
<div class="metric"><div class="metric-label">Context used</div><div class="metric-value">${((ctxUsed/MODEL_CONTEXT)*100).toFixed(1)}%</div><div class="metric-sub">of 128K window</div></div>
125+
<div class="metric"><div class="metric-label">Est. daily cost</div><div class="metric-value">$${cost.toFixed(2)}</div><div class="metric-sub">${callsPerDay.toLocaleString()} calls/day</div></div>
126+
`;
127+
128+
const segs = TYPES.map(t => {
129+
const f = vals[t.id]/MODEL_CONTEXT;
130+
return `<div class="window-seg" style="flex:${f};background:${COLORS[t.id]}"></div>`;
131+
}).join('');
132+
const remF = Math.max(0, (MODEL_CONTEXT-ctxUsed)/MODEL_CONTEXT);
133+
document.getElementById('winviz').innerHTML = segs +
134+
`<div class="window-seg" style="flex:${remF};background:var(--color-background-secondary)"></div>`;
135+
136+
document.getElementById('legend').innerHTML = TYPES.map(t =>
137+
`<div class="leg-item"><div class="leg-dot" style="background:${COLORS[t.id]}"></div>${t.label}</div>`
138+
).join('') + `<div class="leg-item"><div class="leg-dot" style="background:var(--color-background-secondary);border:0.5px solid var(--color-border-tertiary)"></div>Unused window</div>`;
139+
140+
const costRows = TYPES.map(t => {
141+
const price = t.price_out>0?t.price_out:t.price_in;
142+
const dayTotal = (vals[t.id]/1e6)*price*callsPerDay;
143+
return `<tr><td style="color:var(--color-text-secondary)">${t.label}</td>
144+
<td style="text-align:right">${vals[t.id].toLocaleString()} tok</td>
145+
<td style="text-align:right;color:var(--color-text-secondary)">$${price.toFixed(2)}/1M</td>
146+
<td style="text-align:right">$${dayTotal.toFixed(2)}/day</td></tr>`;
147+
}).join('');
148+
document.getElementById('costtable').innerHTML = costRows +
149+
`<tr class="total"><td>Total</td><td></td><td></td><td style="text-align:right">$${cost.toFixed(2)}/day</td></tr>`;
150+
}
151+
152+
const slidersEl = document.getElementById('sliders');
153+
TYPES.forEach(t => {
154+
const row = document.createElement('div');
155+
row.className = 'slider-row';
156+
row.innerHTML = `<label title="${t.desc}">${t.label}</label>
157+
<input type="range" min="${t.min}" max="${t.max}" step="${t.step}" value="${t.def}" id="sl_${t.id}">
158+
<span id="sv_${t.id}">${t.def.toLocaleString()}</span>`;
159+
slidersEl.appendChild(row);
160+
row.querySelector('input').addEventListener('input', e => {
161+
vals[t.id] = +e.target.value;
162+
document.getElementById('sv_'+t.id).textContent = vals[t.id].toLocaleString();
163+
render();
164+
});
165+
});
166+
167+
const callsRow = document.createElement('div');
168+
callsRow.className = 'slider-row';
169+
callsRow.innerHTML = `<label>Calls per day</label>
170+
<input type="range" min="10" max="50000" step="10" value="500" id="sl_calls">
171+
<span id="sv_calls">500</span>`;
172+
slidersEl.appendChild(callsRow);
173+
callsRow.querySelector('input').addEventListener('input', e => {
174+
callsPerDay = +e.target.value;
175+
document.getElementById('sv_calls').textContent = callsPerDay.toLocaleString();
176+
render();
177+
});
178+
179+
render();
180+
</script>
181+
</body>
182+
</html>

0 commit comments

Comments
 (0)