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