div0-space commited on
Commit
37f99eb
·
verified ·
1 Parent(s): 4bc1c60

Neutral price list labels

Browse files
Files changed (1) hide show
  1. api-tester.html +29 -29
api-tester.html CHANGED
@@ -549,9 +549,9 @@
549
  </div>
550
  </div>
551
  <div class="field">
552
- <label title="Live price list from openrouter.ai/api/v1/models; used when the API reports no cost">Prices (OpenRouter)</label>
553
- <div class="price-status" id="openrouterStatus">loading…</div>
554
- <datalist id="openrouterModels"></datalist>
555
  </div>
556
  </div>
557
 
@@ -937,7 +937,7 @@
937
  updatePrompts('lane-1'); // Generate all 4 canonical prompts
938
  updateLogCount();
939
  updatePresetDropdown();
940
- loadOpenRouterCatalog();
941
  });
942
 
943
  function addLane() {
@@ -979,8 +979,8 @@
979
  </div>
980
  </div>
981
  <div class="field">
982
- <label title="OpenRouter model whose live price estimates this lane's cost; empty = auto from the model name">Price as (OpenRouter model)</label>
983
- <input type="text" id="${laneId}-priceAs" list="openrouterModels" placeholder="auto (e.g. qwen/qwen3.8-flash)">
984
  </div>
985
  <div class="field">
986
  <label>System Prompt (optional)</label>
@@ -1417,55 +1417,55 @@
1417
  return value.length > limit ? `${value.slice(0, limit)}…` : value;
1418
  }
1419
 
1420
- // ===== Live price list: openrouter.ai/api/v1/models (public, CORS *) =====
1421
- const OPENROUTER_CATALOG_URL = 'https://openrouter.ai/api/v1/models';
1422
- const OPENROUTER_CACHE_KEY = 'api-tester-openrouter-catalog';
1423
- const OPENROUTER_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
1424
  // Every gateway alias runs on the fused Qwen3.8-Flash-Next runtime, whose
1425
- // hosted counterpart on OpenRouter is qwen/qwen3.8-flash.
1426
  const FLASH_ALIASES = new Set([
1427
  'chat', 'buddy', 'programmer', 'master', 'vision', 'soap', 'ai-suggestions', 'svetliq', 'gpt-oss-20b'
1428
  ]);
1429
- const openRouterPricing = new Map();
1430
 
1431
- function applyOpenRouterCatalog(models, fetchedAt) {
1432
- openRouterPricing.clear();
1433
  const options = [];
1434
  for (const model of models) {
1435
  if (!model?.id || !model.pricing) continue;
1436
- openRouterPricing.set(model.id, model);
1437
  options.push(`<option value="${escapeHtml(model.id)}">${escapeHtml(model.name || '')}</option>`);
1438
  }
1439
- const datalist = document.getElementById('openrouterModels');
1440
  if (datalist) datalist.innerHTML = options.join('');
1441
- const status = document.getElementById('openrouterStatus');
1442
- if (status) status.textContent = `${openRouterPricing.size} models · ${new Date(fetchedAt).toLocaleString()}`;
1443
  }
1444
 
1445
- async function loadOpenRouterCatalog() {
1446
  try {
1447
- const cached = JSON.parse(localStorage.getItem(OPENROUTER_CACHE_KEY) || 'null');
1448
- if (cached?.models?.length && Date.now() - cached.fetchedAt < OPENROUTER_CACHE_TTL_MS) {
1449
- applyOpenRouterCatalog(cached.models, cached.fetchedAt);
1450
  return;
1451
  }
1452
  } catch (e) {
1453
  // Unreadable cache: fetch a fresh list below.
1454
  }
1455
  try {
1456
- const response = await fetch(OPENROUTER_CATALOG_URL);
1457
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
1458
  const body = await response.json();
1459
  const models = (body.data || []).map(model => ({ id: model.id, name: model.name, pricing: model.pricing }));
1460
  const fetchedAt = Date.now();
1461
- applyOpenRouterCatalog(models, fetchedAt);
1462
  try {
1463
- localStorage.setItem(OPENROUTER_CACHE_KEY, JSON.stringify({ fetchedAt, models }));
1464
  } catch (e) {
1465
  // Storage quota: keep the list in memory for this session only.
1466
  }
1467
  } catch (e) {
1468
- const status = document.getElementById('openrouterStatus');
1469
  if (status) status.textContent = `unavailable (${e.message})`;
1470
  }
1471
  }
@@ -1475,7 +1475,7 @@
1475
  if (explicit) return explicit;
1476
  const name = String(model || '').trim();
1477
  const lower = name.toLowerCase();
1478
- if (openRouterPricing.has(name)) return name;
1479
  if (lower.includes('qwen3.8-27b')) return 'qwen/qwen3.8-27b';
1480
  if (FLASH_ALIASES.has(lower) || lower.includes('qwen3.8-flash')) return 'qwen/qwen3.8-flash';
1481
  return null;
@@ -1714,10 +1714,10 @@
1714
  if (state.reportedCost !== null && state.reportedCost !== undefined && Number.isFinite(reported)) {
1715
  return { value: reported, source: 'reported' };
1716
  }
1717
- const pricing = priceModel ? openRouterPricing.get(priceModel)?.pricing : null;
1718
  const usage = state.usage || {};
1719
  if (!pricing || !Number.isFinite(usage.input_tokens) || !Number.isFinite(usage.output_tokens)) return null;
1720
- // OpenRouter prices are USD per token, as strings.
1721
  const rate = key => {
1722
  const value = Number(pricing[key]);
1723
  return Number.isFinite(value) ? value : null;
 
549
  </div>
550
  </div>
551
  <div class="field">
552
+ <label title="Live model price list; used when the API reports no cost">Price list</label>
553
+ <div class="price-status" id="priceListStatus">loading…</div>
554
+ <datalist id="priceModels"></datalist>
555
  </div>
556
  </div>
557
 
 
937
  updatePrompts('lane-1'); // Generate all 4 canonical prompts
938
  updateLogCount();
939
  updatePresetDropdown();
940
+ loadPriceList();
941
  });
942
 
943
  function addLane() {
 
979
  </div>
980
  </div>
981
  <div class="field">
982
+ <label title="Model whose list price estimates this lane's cost; empty = auto from the model name">Price as</label>
983
+ <input type="text" id="${laneId}-priceAs" list="priceModels" placeholder="auto">
984
  </div>
985
  <div class="field">
986
  <label>System Prompt (optional)</label>
 
1417
  return value.length > limit ? `${value.slice(0, limit)}…` : value;
1418
  }
1419
 
1420
+ // ===== Live model price list (public, CORS *) =====
1421
+ const PRICE_LIST_URL = 'https://openrouter.ai/api/v1/models';
1422
+ const PRICE_LIST_CACHE_KEY = 'api-tester-price-list';
1423
+ const PRICE_LIST_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
1424
  // Every gateway alias runs on the fused Qwen3.8-Flash-Next runtime, whose
1425
+ // hosted counterpart is qwen/qwen3.8-flash.
1426
  const FLASH_ALIASES = new Set([
1427
  'chat', 'buddy', 'programmer', 'master', 'vision', 'soap', 'ai-suggestions', 'svetliq', 'gpt-oss-20b'
1428
  ]);
1429
+ const modelPricing = new Map();
1430
 
1431
+ function applyPriceList(models, fetchedAt) {
1432
+ modelPricing.clear();
1433
  const options = [];
1434
  for (const model of models) {
1435
  if (!model?.id || !model.pricing) continue;
1436
+ modelPricing.set(model.id, model);
1437
  options.push(`<option value="${escapeHtml(model.id)}">${escapeHtml(model.name || '')}</option>`);
1438
  }
1439
+ const datalist = document.getElementById('priceModels');
1440
  if (datalist) datalist.innerHTML = options.join('');
1441
+ const status = document.getElementById('priceListStatus');
1442
+ if (status) status.textContent = `${modelPricing.size} models · ${new Date(fetchedAt).toLocaleString()}`;
1443
  }
1444
 
1445
+ async function loadPriceList() {
1446
  try {
1447
+ const cached = JSON.parse(localStorage.getItem(PRICE_LIST_CACHE_KEY) || 'null');
1448
+ if (cached?.models?.length && Date.now() - cached.fetchedAt < PRICE_LIST_CACHE_TTL_MS) {
1449
+ applyPriceList(cached.models, cached.fetchedAt);
1450
  return;
1451
  }
1452
  } catch (e) {
1453
  // Unreadable cache: fetch a fresh list below.
1454
  }
1455
  try {
1456
+ const response = await fetch(PRICE_LIST_URL);
1457
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
1458
  const body = await response.json();
1459
  const models = (body.data || []).map(model => ({ id: model.id, name: model.name, pricing: model.pricing }));
1460
  const fetchedAt = Date.now();
1461
+ applyPriceList(models, fetchedAt);
1462
  try {
1463
+ localStorage.setItem(PRICE_LIST_CACHE_KEY, JSON.stringify({ fetchedAt, models }));
1464
  } catch (e) {
1465
  // Storage quota: keep the list in memory for this session only.
1466
  }
1467
  } catch (e) {
1468
+ const status = document.getElementById('priceListStatus');
1469
  if (status) status.textContent = `unavailable (${e.message})`;
1470
  }
1471
  }
 
1475
  if (explicit) return explicit;
1476
  const name = String(model || '').trim();
1477
  const lower = name.toLowerCase();
1478
+ if (modelPricing.has(name)) return name;
1479
  if (lower.includes('qwen3.8-27b')) return 'qwen/qwen3.8-27b';
1480
  if (FLASH_ALIASES.has(lower) || lower.includes('qwen3.8-flash')) return 'qwen/qwen3.8-flash';
1481
  return null;
 
1714
  if (state.reportedCost !== null && state.reportedCost !== undefined && Number.isFinite(reported)) {
1715
  return { value: reported, source: 'reported' };
1716
  }
1717
+ const pricing = priceModel ? modelPricing.get(priceModel)?.pricing : null;
1718
  const usage = state.usage || {};
1719
  if (!pricing || !Number.isFinite(usage.input_tokens) || !Number.isFinite(usage.output_tokens)) return null;
1720
+ // List prices are USD per token, as strings.
1721
  const rate = key => {
1722
  const value = Number(pricing[key]);
1723
  return Number.isFinite(value) ? value : null;