/* ========================================================================= pricing.js — fetch /api/pricing, render the rate table + cost estimator • populates the model dropdown for the calculator • recomputes credits on any input change • live filter on the rate table ========================================================================= */ (function () { 'use strict'; var API = '/api/pricing'; var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]; }); } function fmtPrice(n) { if (n == null) return '—'; if (n < 0.01) return '$' + n.toFixed(4); if (n < 1) return '$' + n.toFixed(3); return '$' + n.toFixed(2); } function fmtCredits(n) { return Number(n || 0).toLocaleString(); } // strip the leading "free:" or "anthropic/" prefix for a friendlier display, // but keep the full id as a title tooltip function shortName(full) { var n = full.replace(/^free:/, ''); var slash = n.indexOf('/'); return slash >= 0 ? n.slice(slash + 1) : n; } function providerOf(full) { var n = full.replace(/^free:/, ''); var slash = n.indexOf('/'); return slash >= 0 ? n.slice(0, slash) : ''; } var STATE = { models: [], rate: null }; async function load() { var body = document.getElementById('rates-body'); try { var res = await fetch(API); if (!res.ok) throw new Error('status ' + res.status); var data = await res.json(); STATE.models = data.models || []; STATE.rate = data.rate || { credits_per_usd: 10000, input_ratio: 1 / 3 }; renderTable(STATE.models); populateCalculator(STATE.models); } catch (e) { if (body) body.innerHTML = 'Couldn\u2019t load rates. Please try again.'; } } function renderTable(models) { var body = document.getElementById('rates-body'); if (!body) return; if (!models.length) { body.innerHTML = 'No models match your filter.'; return; } var html = ''; for (var i = 0; i < models.length; i++) { var m = models[i]; var prov = providerOf(m.model); var badge = m.free ? 'free' : ''; html += '' + '' + esc(shortName(m.model)) + '' + badge + (prov ? '
' + esc(prov) + '
' : '') + '' + '' + fmtPrice(m.usd_per_1m_input) + '' + '' + fmtPrice(m.usd_per_1m_output) + '' + '' + fmtCredits(m.credits_per_1k_output) + '' + '' + fmtCredits(m.targets) + '' + ''; } body.innerHTML = html; } function populateCalculator(models) { var sel = document.getElementById('calc-model'); if (!sel) return; // sort: non-free (claude) first, then by output price descending so premium surfaces var sorted = models.slice().sort(function (a, b) { if (a.free !== b.free) return a.free ? 1 : -1; return b.usd_per_1m_output - a.usd_per_1m_output; }); var opts = ''; for (var i = 0; i < sorted.length; i++) { opts += ''; } sel.innerHTML = opts; recalc(); } function lookup(name) { for (var i = 0; i < STATE.models.length; i++) { if (STATE.models[i].model === name) return STATE.models[i]; } return null; } function recalc() { var sel = document.getElementById('calc-model'); var inEl = document.getElementById('calc-in'); var outEl = document.getElementById('calc-out'); var credEl = document.getElementById('calc-result').querySelector('.cr-credits'); var usdEl = document.getElementById('calc-usd'); if (!sel || !inEl || !outEl || !credEl) return; var m = lookup(sel.value); var tokIn = Math.max(0, parseInt(inEl.value, 10) || 0); var tokOut = Math.max(0, parseInt(outEl.value, 10) || 0); if (!m) { credEl.textContent = '—'; usdEl.textContent = ''; return; } // input priced at 1/3 of output var inPrice = m.usd_per_1m_output * STATE.rate.input_ratio; var usd = (tokIn * inPrice + tokOut * m.usd_per_1m_output) / 1e6; var credits = Math.round(usd * (STATE.rate.credits_per_usd || 10000)); if ((tokIn || tokOut) && credits < 1) credits = 1; if (!tokIn && !tokOut) credits = 0; credEl.textContent = fmtCredits(credits); usdEl.textContent = credits > 0 ? ('\u2248 $' + (credits / (STATE.rate.credits_per_usd || 10000)).toFixed(4) + ' USD') : ''; } // wire up events once DOM is ready function init() { load(); var sel = document.getElementById('calc-model'); var inEl = document.getElementById('calc-in'); var outEl = document.getElementById('calc-out'); var search = document.getElementById('rate-search'); if (sel) sel.addEventListener('change', recalc); if (inEl) inEl.addEventListener('input', recalc); if (outEl) outEl.addEventListener('input', recalc); if (search) search.addEventListener('input', function () { var q = search.value.trim().toLowerCase(); if (!q) { renderTable(STATE.models); return; } renderTable(STATE.models.filter(function (m) { return m.model.toLowerCase().indexOf(q) >= 0; })); }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();