summaryrefslogtreecommitdiff
path: root/internal/adminui/assets/app.js
blob: f2ed886d206c3fbf1ff2d527199912fc0d1711ab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
const state = {
  token: '', csrf: '', actor: {}, permissions: new Set(), overview: {},
  tenants: [], projects: [], keys: [], providers: [], models: [], billingAccounts: [], ledger: [],
  usage: [], usagePaging: {cursor:'',nextCursor:'',history:[]}, usageSummary: [], usageDaily: [], usageAnalytics: {models:[],providers:[],keys:[]}, limits: [], users: [], audit: [], orders: [], refunds: [], disputes: [], invoices: [], sessions: [],
  developerConfig: {base_url:'',endpoints:{}}, developerModels: [], preferences: {},
  autoTopUp: {}, billingProfile: {},
  mfa: {totp_enabled:false,passkeys:[]}, pendingMFA: null, authConfig: {}, playgroundKey: '', playgroundController: null, detailModel: null, detailUsage: null
};
const $ = (selector) => document.querySelector(selector);
const $$ = (selector) => [...document.querySelectorAll(selector)];
const can = (permission) => state.permissions.has(permission);

function esc(value) { return String(value ?? '').replace(/[&<>'"]/g, (char) => ({ '&':'&amp;', '<':'&lt;', '>':'&gt;', "'":'&#39;', '"':'&quot;' }[char])); }
function date(value) { return value ? new Date(value).toLocaleString() : '—'; }
function shortID(value) { const text = String(value || ''); return text ? `${text.slice(0, 10)}${text.length > 10 ? '…' : ''}` : '—'; }
function percent(part, total) { return total ? `${Math.round((part / total) * 100)}%` : '—'; }
function toast(message, error = false) { const node = $('#toast'); node.textContent = message; node.className = `toast visible ${error ? 'error' : ''}`; setTimeout(() => { node.className = 'toast'; }, 3200); }
async function api(path, options = {}) {
  const method = (options.method || 'GET').toUpperCase();
  const headers = { ...(options.body ? {'Content-Type':'application/json'} : {}), ...(options.headers || {}) };
  if (state.token) headers.Authorization = `Bearer ${state.token}`;
  if (!['GET','HEAD','OPTIONS'].includes(method) && state.csrf && !path.startsWith('/auth/login') && !path.startsWith('/auth/register')) headers['X-CSRF-Token'] = state.csrf;
  const response = await fetch(`./api${path}`, { ...options, method, credentials:'same-origin', headers });
  const payload = await response.json().catch(() => ({}));
  if (!response.ok) { const error = new Error(payload?.error?.message || `Request failed (${response.status})`); error.status=response.status; error.type=payload?.error?.type; throw error; }
  return payload;
}
function setConnected(connected) {
  $('#auth-screen').classList.toggle('hidden', connected);
  $('#console-app').classList.toggle('hidden', !connected);
  if (!connected) { state.playgroundController?.abort();state.playgroundController=null;state.playgroundKey=''; const key=$('#playground-key');if(key)key.value=''; return; }
  $('#connection-state').textContent = state.actor.role?.replaceAll('_', ' ') || 'connected';
  $('#actor-label').textContent = state.actor.display_name || state.actor.email || 'Operator';
}
function formJSON(form) {
  const data = new FormData(form);
  // Password-manager username hints are intentionally decoys. They must not
  // be sent to strict JSON endpoints that only accept the documented fields.
  data.delete('username');
  return Object.fromEntries(data.entries());
}
function selectOptions(items, valueKey, labelKey, empty = 'Select…') { return `<option value="">${empty}</option>${items.map(item => `<option value="${esc(item[valueKey])}">${esc(item[labelKey])}</option>`).join('')}`; }
function decimalToScaled(value, digits) {
  const match = String(value).trim().match(/^(-?)(\d+)(?:\.(\d+))?$/); if (!match) throw new Error('Enter a valid decimal amount');
  const fraction = match[3] || ''; if (fraction.length > digits && /[1-9]/.test(fraction.slice(digits))) throw new Error(`Use at most ${digits} decimal places`);
  const scale = 10n ** BigInt(digits); const absolute = BigInt(match[2]) * scale + BigInt((fraction.slice(0, digits) + '0'.repeat(digits)).slice(0, digits) || '0');
  const result = Number(match[1] ? -absolute : absolute); if (!Number.isSafeInteger(result)) throw new Error('Amount is too large'); return result;
}
function scaledToDecimal(value, digits) { const number = BigInt(value || 0); const whole = number / (10n ** BigInt(digits)); const fraction = String(number % (10n ** BigInt(digits))).padStart(digits, '0').replace(/0+$/, ''); return fraction ? `${whole}.${fraction}` : String(whole); }
function currencyDigits(currency) { return ['bif','clp','djf','gnf','jpy','kmf','krw','mga','pyg','rwf','ugx','vnd','vuv','xaf','xof','xpf'].includes(currency) ? 0 : ['bhd','jod','kwd','omr','tnd'].includes(currency) ? 3 : 2; }
function money(micros, currency = state.overview.billing_currency || 'usd') { return new Intl.NumberFormat(undefined, { style:'currency', currency:currency.toUpperCase(), minimumFractionDigits:2, maximumFractionDigits:6 }).format(Number(micros || 0) / 1_000_000); }
function integer(value) { return new Intl.NumberFormat().format(Number(value || 0)); }
function chartHeightClass(value, maximum) { return `chart-height-${Math.max(1, Math.min(20, Math.ceil(Number(value || 0) / Math.max(1, Number(maximum || 0)) * 20)))}`; }
function emptyRow(span) { return `<tr><td colspan="${span}" class="empty">No records yet</td></tr>`; }
function showSecret(title, value) { $('#secret-title').textContent = title; $('#created-secret').textContent = value; $('#secret-dialog').showModal(); }
function cookie(name) { const prefix=`${encodeURIComponent(name)}=`; const value=document.cookie.split('; ').find(item=>item.startsWith(prefix)); return value ? decodeURIComponent(value.slice(prefix.length)) : ''; }
function authError(message='', success=false) { const node=$('#auth-error');node.textContent=message;node.classList.toggle('success',Boolean(message)&&success); }
function showAuthPane(id) {
  $$('.auth-pane').forEach(node => node.classList.toggle('active', node.id === id));
  $$('.auth-tab').forEach(node => node.classList.toggle('active', node.dataset.authPane === id));
}

function base64URLBytes(value) {
  const normalized=String(value).replaceAll('-','+').replaceAll('_','/');
  const padded=normalized+'='.repeat((4-normalized.length%4)%4);
  return Uint8Array.from(atob(padded),char=>char.charCodeAt(0));
}
function bytesBase64URL(value) {
  if (value == null) return null;
  const bytes=new Uint8Array(value); let text=''; bytes.forEach(byte=>{text+=String.fromCharCode(byte);});
  return btoa(text).replaceAll('+','-').replaceAll('/','_').replace(/=+$/,'');
}
function browserPublicKey(options) {
  const value=structuredClone(options.publicKey||options);
  value.challenge=base64URLBytes(value.challenge);
  if(value.user?.id)value.user.id=base64URLBytes(value.user.id);
  if(value.excludeCredentials)value.excludeCredentials=value.excludeCredentials.map(item=>({...item,id:base64URLBytes(item.id)}));
  if(value.allowCredentials)value.allowCredentials=value.allowCredentials.map(item=>({...item,id:base64URLBytes(item.id)}));
  return value;
}
function browserCredential(value) {
  const response={clientDataJSON:bytesBase64URL(value.response.clientDataJSON)};
  for(const key of ['attestationObject','authenticatorData','signature','userHandle']){
    if(value.response[key]!=null)response[key]=bytesBase64URL(value.response[key]);
  }
  if(typeof value.response.getTransports==='function')response.transports=value.response.getTransports();
  return {id:value.id,rawId:bytesBase64URL(value.rawId),type:value.type,response,
    authenticatorAttachment:value.authenticatorAttachment,clientExtensionResults:value.getClientExtensionResults()};
}
async function createPasskey(options) {
  const credential=await navigator.credentials.create({publicKey:browserPublicKey(options)});
  if(!credential)throw new Error('Passkey creation was cancelled');
  return browserCredential(credential);
}
async function getPasskey(options) {
  const credential=await navigator.credentials.get({publicKey:browserPublicKey(options)});
  if(!credential)throw new Error('Passkey authentication was cancelled');
  return browserCredential(credential);
}

async function permitted(permission, path) { if (!can(permission)) return []; return api(path); }
function defaultUsageQuery() { const to=new Date();const from=new Date(to.getTime()-29*86400000);return new URLSearchParams({from:from.toISOString().slice(0,10),to:to.toISOString().slice(0,10)}).toString(); }
async function loadAll(knownSession = null) {
  try {
    const session = knownSession || await api('/me'); state.actor = session.actor || {}; state.permissions = new Set(session.permissions || []);
    state.overview = await api('/overview');
    const usageQuery=defaultUsageQuery();
    const results = await Promise.all([
      permitted('tenants.read','/tenants'), permitted('projects.read','/projects'), permitted('keys.read','/keys'),
      permitted('platform.read','/providers'), permitted('platform.read','/models'), permitted('overview.read','/developer/config'),
      permitted('overview.read','/developer/models'), can('preferences.read') ? api('/developer/preferences') : {}, permitted('usage.read',`/usage?${usageQuery}&limit=50`),
      permitted('usage.read','/usage/summary'), permitted('limits.read','/limits'), permitted('users.read','/users'),
      permitted('audit.read','/audit'), state.overview.billing_enabled ? permitted('billing.read','/billing/accounts') : [],
      state.overview.billing_enabled ? permitted('billing.read','/billing/ledger') : [],
      state.actor.id ? api('/auth/mfa') : {totp_enabled:false,passkeys:[]},
      state.actor.id ? api('/auth/sessions') : [],
      state.overview.billing_enabled && can('billing.read') ? api('/billing/orders') : [],
      state.overview.billing_enabled ? permitted('billing.read','/billing/refunds') : [],
      state.overview.billing_enabled ? permitted('billing.read','/billing/disputes') : [],
      state.overview.billing_enabled ? permitted('billing.read','/billing/invoices') : [],
      permitted('usage.read',`/usage/daily?${usageQuery}`), permitted('usage.read',`/usage/analytics?${usageQuery}`),
      state.overview.billing_enabled && state.actor.tenant_id && can('billing.read') ? api('/billing/auto-topup') : {},
      state.overview.billing_enabled && state.actor.tenant_id && can('billing.read') ? api('/billing/profile') : {}
    ]);
    [state.tenants,state.projects,state.keys,state.providers,state.models,state.developerConfig,state.developerModels,state.preferences,state.usage,state.usageSummary,state.limits,state.users,state.audit,state.billingAccounts,state.ledger,state.mfa,state.sessions,state.orders,state.refunds,state.disputes,state.invoices,state.usageDaily,state.usageAnalytics,state.autoTopUp,state.billingProfile] = results;
    const usagePage=state.usage;state.usage=Array.isArray(usagePage)?usagePage:(usagePage?.data||[]);state.usagePaging={cursor:'',nextCursor:usagePage?.next_cursor||'',history:[]};
    renderAll(); setConnected(true); return true;
  } catch (error) { setConnected(false); if (error.status !== 401) toast(error.message, true); return false; }
}

function applyPermissions() {
  $$('[data-permission]').forEach(node => node.classList.toggle('hidden', !can(node.dataset.permission)));
  $('#billing-tab').classList.toggle('hidden', !state.overview.billing_enabled || !can('billing.read'));
  $('#topup-form').classList.toggle('hidden', !state.overview.stripe_enabled || !can('billing.topup'));
  $('#billing-portal').classList.toggle('hidden', !state.overview.stripe_enabled || !can('billing.topup'));
  $('#account-tab').classList.toggle('hidden', !state.actor.id);
  $('#developer-preferences-form').classList.toggle('hidden', !state.actor.tenant_id || !can('preferences.read'));
  $('#billing-preferences-form').classList.toggle('hidden', !state.actor.tenant_id || !state.overview.billing_enabled || !can('billing.read'));
  $('#auto-topup-panel').classList.toggle('hidden', !state.actor.tenant_id || !state.overview.billing_enabled || !can('billing.read'));
  $('#billing-profile-panel').classList.toggle('hidden', !state.actor.tenant_id || !state.overview.billing_enabled || !can('billing.read'));
  const active = $('.tab.active'); if (active?.classList.contains('hidden')) $('.tab[data-section="quickstart"]').click();
}
function renderAll() {
  applyPermissions(); renderOverview(); renderQuickstart(); renderPreferences(); renderCatalog(); renderTenants(); renderProjects(); renderKeys(); renderProviders(); renderModels(); renderBilling();
  renderUsage(); renderLimits(); renderUsers(); renderAudit(); renderRouteEditor(); renderSecurity();
}
function renderOverview() {
  const data = state.overview; const propagation = !data.redis_configured ? 'PG polling' : data.redis_connected ? 'Redis live' : 'PG fallback';
  const now = new Date(); const current = state.usageSummary.filter(item => { const period = new Date(item.period_start); return period.getUTCMonth() === now.getUTCMonth() && period.getUTCFullYear() === now.getUTCFullYear(); });
  const totals = current.reduce((acc,item) => { acc.requests += item.request_count; acc.success += item.successful_requests; acc.tokens += item.total_tokens; acc.cost += item.cost_micros; return acc; }, {requests:0,success:0,tokens:0,cost:0});
  const items = [['Requests', integer(totals.requests), 'this month'], ['Success rate', percent(totals.success,totals.requests), 'completed requests'], ['Tokens', integer(totals.tokens), 'this month'], ['Spend', money(totals.cost), 'metered cost'], ['Projects', data.projects, 'active workspaces'], ['Propagation', propagation, `generation ${data.runtime_generation}`]];
  $('#metrics').innerHTML = items.map(([label,value,sub]) => `<article class="metric"><span>${label}</span><strong>${esc(value)}</strong><small>${esc(sub)}</small></article>`).join('');
  $('#overview-usage-body').innerHTML = current.map(item => `<tr><td><strong>${esc(item.project_name)}</strong></td><td>${integer(item.request_count)}</td><td>${percent(item.successful_requests,item.request_count)}</td><td>${integer(item.total_tokens)}</td><td>${money(item.cost_micros)}</td><td class="${item.uncollected_micros ? 'money-negative':''}">${money(item.uncollected_micros)}</td></tr>`).join('') || emptyRow(6);
}
function goTo(section) { const node=$(`.tab[data-section="${section}"]`); if(node){ node.click(); window.scrollTo({top:0,behavior:'smooth'}); } }
function latestAvailableBalance() { return state.billingAccounts.find(item => !state.actor.tenant_id || item.tenant_id===state.actor.tenant_id)?.available_micros || 0; }
function inferenceBaseURL() { return String(state.developerConfig.base_url||window.location.origin).replace(/\/$/,''); }
function developerEndpoint(name) { const defaults={chat_completions:'/v1/chat/completions',responses:'/v1/responses',embeddings:'/v1/embeddings',messages:'/anthropic/v1/messages',models:'/v1/models'};const path=state.developerConfig.endpoints?.[name]||defaults[name]||'';return `${inferenceBaseURL()}${path}`; }
function modelIsUnavailable(model) { return model?.health_status==='unavailable'; }
function apiKeyDisplay(item) { return `${item?.key_prefix||''}${item?.key_suffix||''}`; }
function developerModelOption(item) { const unavailable=modelIsUnavailable(item);return `<option value="${esc(item.public_id)}" ${unavailable?'disabled':''}>${esc(item.display_name||item.public_id)}${unavailable?' (unavailable)':''}</option>`; }
function providerHealthSummary(item) {
  const total=Number(item.provider_count||0);const available=Number(item.available_provider_count||0);const status=item.health_status||'online';
  if(status==='unavailable')return {status,label:'Unavailable',detail:'No provider is currently accepting traffic'};
  if(status==='degraded')return {status,label:'Degraded',detail:`${available} of ${total} providers accepting traffic`};
  return {status:'online',label:'Online',detail:total?`${available} of ${total} providers accepting traffic`:'Routing is available'};
}
function renderQuickstart() {
  const balance=latestAvailableBalance();
  const balanceText=state.overview.billing_enabled && can('billing.read') ? `Available balance ${money(balance)}` : state.overview.billing_enabled ? 'Balance managed by billing admin' : 'Prepaid billing disabled';
  $('#quickstart-balance').textContent=balanceText;
  const hasKey=state.keys.some(item=>item.status==='active');
  const hasUsage=state.usage.some(item=>item.success);
  const funded=!state.overview.billing_enabled || !can('billing.read') || balance>0;
  const steps=[
    {title:'Workspace ready',body:'Your account and default project are ready.',done:Boolean(state.actor.tenant_id||state.actor.bootstrap),action:'projects'},
    {title:'Add balance',body:state.overview.billing_enabled?(!can('billing.read')?'Billing is managed by workspace billing members.':funded?'Funds are available for inference.':'Add funds before the first billable request.'):'Billing is not enabled.',done:funded,action:can('billing.read')?'billing':'team'},
    {title:'Create an API key',body:hasKey?'An active key can call the gateway.':'Create a key and copy its secret once.',done:hasKey,action:hasKey?'keys':'',target:hasKey?'':'starter-key-form'},
    {title:'Make a request',body:hasUsage?'Your first successful request is recorded.':'Run the example below and watch it appear here.',done:hasUsage,action:hasUsage?'usage':'',target:hasUsage?'':'playground-form'}
  ];
  $('#onboarding').innerHTML=steps.map(step=>`<button type="button" class="onboarding-step ${step.done?'done':''}" ${step.action?`data-goto="${esc(step.action)}"`:''} ${step.target?`data-scroll-to="${esc(step.target)}"`:''}><strong>${step.done?'✓ ':''}${esc(step.title)}</strong><small>${esc(step.body)}</small><span class="step-state">${step.done?'Complete':'Open step →'}</span></button>`).join('');
  const signals=[['Models available',integer(state.developerModels.length)],['Active API keys',integer(state.keys.filter(item=>item.status==='active').length)],['This month',money(state.usageSummary.reduce((sum,item)=>sum+Number(item.cost_micros||0),0))],['Auto top-up',state.autoTopUp.enabled?'Enabled':state.autoTopUp.payment_method_configured?'Ready':'Not configured'],['Fallback model',state.preferences.fallback_model||'Not configured'],['Runtime',state.overview.redis_connected?'Live propagation':'PG fallback']];
  $('#quickstart-signals').innerHTML=signals.map(([label,value])=>`<div class="signal"><span>${esc(label)}</span><strong>${esc(value)}</strong></div>`).join('');
  const recent=state.usage.slice(0,5);
  $('#quickstart-usage-body').innerHTML=recent.map(item=>`<tr><td>${date(item.started_at)}</td><td><strong>${esc(item.public_model)}</strong></td><td><span class="badge ${item.success?'active':'suspended'}">${item.status_code}</span></td><td>${integer(item.total_tokens)}</td><td>${money(item.charged_micros||item.cost_micros)}</td><td>${integer(item.duration_ms)} ms</td></tr>`).join('')||emptyRow(6);
  const modelSelect=$('#quickstart-model'); const previous=modelSelect.value||state.preferences.default_model;
  modelSelect.innerHTML=state.developerModels.map(developerModelOption).join('')||'<option value="">No available model</option>';
  if(state.developerModels.some(item=>item.public_id===previous&&!modelIsUnavailable(item)))modelSelect.value=previous;
  else{const firstAvailable=state.developerModels.find(item=>!modelIsUnavailable(item));if(firstAvailable)modelSelect.value=firstAvailable.public_id;}
  const playgroundModel=$('#playground-model'); const playgroundPrevious=playgroundModel.value||modelSelect.value;
  playgroundModel.innerHTML=modelSelect.innerHTML;
  if(state.developerModels.some(item=>item.public_id===playgroundPrevious&&!modelIsUnavailable(item)))playgroundModel.value=playgroundPrevious;
  if(state.playgroundKey)$('#playground-key').value=state.playgroundKey;
  $('#playground-endpoint').textContent=inferenceBaseURL();
  syncQuickstartProtocols(); syncPlaygroundProtocols(); renderQuickstartCode(); renderDeveloperAccess();
}
function renderDeveloperAccess() {
  const form=$('#starter-key-form');const projects=state.projects.filter(item=>item.status==='active'&&(!state.actor.tenant_id||item.tenant_id===state.actor.tenant_id));const select=$('#starter-project');const previous=select.value;
  select.innerHTML=projects.map(item=>`<option value="${esc(item.id)}">${esc(item.name)}</option>`).join('')||'<option value="">No active project</option>';
  if(projects.some(item=>item.id===previous))select.value=previous;else{const preferred=projects.find(item=>item.slug==='default')||projects[0];if(preferred)select.value=preferred.id;}
  const model=selectedDeveloperModel();$('#starter-model-label').textContent=model.public_id||'No model';
  form.classList.toggle('hidden',!state.actor.tenant_id||!can('keys.write'));
  $('#starter-key-submit').disabled=!select.value||!model.public_id||modelIsUnavailable(model);
  const rows=[['OpenAI SDK base',`${inferenceBaseURL()}/v1`],['Anthropic SDK base',`${inferenceBaseURL()}/anthropic`],['Chat Completions',developerEndpoint('chat_completions')],['Responses',developerEndpoint('responses')],['Embeddings',developerEndpoint('embeddings')],['Anthropic Messages',developerEndpoint('messages')],['Models',developerEndpoint('models')]];
  $('#endpoint-list').innerHTML=rows.map(([label,value],index)=>`<div class="endpoint-row"><span>${esc(label)}</span><code>${esc(value)}</code><button class="text-button" type="button" data-copy-endpoint="${index}">Copy</button></div>`).join('');
  $('#endpoint-list').dataset.values=JSON.stringify(rows.map(([,value])=>value));
  $('#endpoint-env').textContent=`export AIGW_API_KEY="your-key"\nexport OPENAI_BASE_URL="${inferenceBaseURL()}/v1"\nexport ANTHROPIC_BASE_URL="${inferenceBaseURL()}/anthropic"`;
}
function renderPreferences() {
  const preferences=state.preferences||{}; const modelOptions=state.developerModels.map(item=>`<option value="${esc(item.public_id)}">${esc(item.display_name||item.public_id)}</option>`).join('');
  const defaultSelect=$('#preference-default-model'); const fallbackSelect=$('#preference-fallback-model');
  defaultSelect.innerHTML=`<option value="">First available model</option>${modelOptions}`;
  fallbackSelect.innerHTML=`<option value="">No workspace fallback</option>${modelOptions}`;
  if(state.developerModels.some(item=>item.public_id===preferences.default_model))defaultSelect.value=preferences.default_model;
  if(state.developerModels.some(item=>item.public_id===preferences.fallback_model))fallbackSelect.value=preferences.fallback_model;
  const canWriteDeveloper=can('developer.preferences.write'); defaultSelect.disabled=!canWriteDeveloper; fallbackSelect.disabled=!canWriteDeveloper;
  $('#low-balance-enabled').checked=preferences.low_balance_enabled!==false;
  $('#low-balance-threshold').value=scaledToDecimal(preferences.low_balance_threshold_micros||0,6);
  $('#spend-anomaly-enabled').checked=preferences.spend_anomaly_enabled!==false;
  $('#spend-anomaly-multiplier').value=Number(preferences.spend_anomaly_multiplier||3);
  $('#spend-anomaly-minimum').value=scaledToDecimal(preferences.spend_anomaly_min_micros||0,6);
  const canWriteBilling=can('billing.preferences.write');for(const id of ['low-balance-enabled','low-balance-threshold','spend-anomaly-enabled','spend-anomaly-multiplier','spend-anomaly-minimum'])$(`#${id}`).disabled=!canWriteBilling;
  $('#balance-alert-status').textContent=state.authConfig.email_delivery_enabled?'Verified billing members receive at most one alert of each type per day. Anomaly spend compares today with the previous seven-day daily average.':'These preferences are saved now and activate when SMTP delivery is configured.';
}
function selectedDeveloperModel() { return state.developerModels.find(item=>item.public_id===$('#quickstart-model')?.value) || state.developerModels.find(item=>!modelIsUnavailable(item)) || state.developerModels[0] || {}; }
function syncQuickstartProtocols() {
  const model=selectedDeveloperModel(); const select=$('#quickstart-protocol'); const previous=select.value;
  select.innerHTML=(model.supported_wire_apis||[]).map(apiName=>`<option value="${esc(apiName)}">${esc(modelProtocolLabel(apiName))}</option>`).join('')||'<option value="chat_completions">OpenAI Chat Completions</option>';
  if((model.supported_wire_apis||[]).includes(previous))select.value=previous;
  syncProviderSelect('#quickstart-provider',model,select.value);
}
function selectedPlaygroundModel() { return state.developerModels.find(item=>item.public_id===$('#playground-model')?.value) || state.developerModels.find(item=>!modelIsUnavailable(item)) || state.developerModels[0] || {}; }
function syncPlaygroundProtocols() {
  const model=selectedPlaygroundModel(); const select=$('#playground-protocol'); const previous=select.value;
  select.innerHTML=(model.supported_wire_apis||[]).map(apiName=>`<option value="${esc(apiName)}">${esc(modelProtocolLabel(apiName))}</option>`).join('')||'<option value="chat_completions">OpenAI Chat Completions</option>';
  if((model.supported_wire_apis||[]).includes(previous))select.value=previous;
  syncProviderSelect('#playground-provider',model,select.value);
  const embeddings=select.value==='embeddings';const maxField=$('#playground-max-output-field');
  maxField.classList.toggle('hidden',embeddings);$('#playground-max-output').required=!embeddings;
  const max=Number(model.max_output_tokens||4096);$('#playground-max-output').max=String(max>0?max:4096);
  if(Number($('#playground-max-output').value)>max&&max>0)$('#playground-max-output').value=String(max);
}
function syncProviderSelect(selector,model,wire) {
  const select=$(selector);const previous=select.value;const providers=(model.providers||[]).filter(item=>item.wire_api===wire);
  select.innerHTML=`<option value="">Automatic routing</option>${providers.map(item=>`<option value="${esc(item.slug)}" ${item.state==='open'?'disabled':''}>${esc(item.name||item.slug)} (${esc(item.slug)})${item.state==='open'?' — unavailable':''}</option>`).join('')}`;
  if(providers.some(item=>item.slug===previous&&item.state!=='open'))select.value=previous;
}
function modelSelector(model,providerSelector) { const provider=$(providerSelector)?.value||'';const publicID=model.public_id||'model-id';return provider?`${publicID}:${provider}`:publicID; }
function quickstartEndpoint(wire) { return wire==='messages'?developerEndpoint('messages'):wire==='responses'?developerEndpoint('responses'):wire==='embeddings'?developerEndpoint('embeddings'):developerEndpoint('chat_completions'); }
function renderQuickstartCode() {
  const model=selectedDeveloperModel(); const selectedModel=modelSelector(model,'#quickstart-provider'); const wire=$('#quickstart-protocol').value||'chat_completions'; const language=$('#quickstart-language').value||'curl'; const endpoint=quickstartEndpoint(wire); const key='$AIGW_API_KEY';
  const base=String(state.developerConfig.base_url||window.location.origin).replace(/\/$/,'');let code='';
  if(language==='curl') {
    const headers=wire==='messages'?`-H "x-api-key: ${key}"\n  -H "anthropic-version: 2023-06-01"`:`-H "Authorization: Bearer ${key}"`;
    const body=wire==='messages'?`{"model":"${selectedModel}","max_tokens":256,"messages":[{"role":"user","content":"Say hello in one sentence."}]}`:wire==='responses'?`{"model":"${selectedModel}","input":"Say hello in one sentence."}`:wire==='embeddings'?`{"model":"${selectedModel}","input":"AIGW semantic search"}`:`{"model":"${selectedModel}","messages":[{"role":"user","content":"Say hello in one sentence."}],"stream":false}`;
    code=`export AIGW_API_KEY="your-key"\ncurl ${endpoint} \\\n  ${headers} \\\n  -H "Content-Type: application/json" \\\n  -d '${body}'`;
  } else if(language==='python') {
    if(wire==='messages')code=`import os\nfrom anthropic import Anthropic\n\nclient = Anthropic(api_key=os.environ["AIGW_API_KEY"], base_url="${base}/anthropic")\nmessage = client.messages.create(model="${selectedModel}", max_tokens=256, messages=[{"role":"user", "content":"Say hello in one sentence."}])\nprint(message.content[0].text)`;
    else if(wire==='responses')code=`import os\nfrom openai import OpenAI\n\nclient = OpenAI(api_key=os.environ["AIGW_API_KEY"], base_url="${base}/v1")\nresponse = client.responses.create(model="${selectedModel}", input="Say hello in one sentence.")\nprint(response.output_text)`;
    else if(wire==='embeddings')code=`import os\nfrom openai import OpenAI\n\nclient = OpenAI(api_key=os.environ["AIGW_API_KEY"], base_url="${base}/v1")\nresponse = client.embeddings.create(model="${selectedModel}", input="AIGW semantic search")\nprint(len(response.data[0].embedding))`;
    else code=`import os\nfrom openai import OpenAI\n\nclient = OpenAI(api_key=os.environ["AIGW_API_KEY"], base_url="${base}/v1")\nresponse = client.chat.completions.create(model="${selectedModel}", messages=[{"role":"user", "content":"Say hello in one sentence."}])\nprint(response.choices[0].message.content)`;
  } else {
    if(wire==='messages')code=`import Anthropic from "@anthropic-ai/sdk";\n\nconst client = new Anthropic({ apiKey: process.env.AIGW_API_KEY, baseURL: "${base}/anthropic" });\nconst message = await client.messages.create({ model: "${selectedModel}", max_tokens: 256, messages: [{ role: "user", content: "Say hello in one sentence." }] });\nconsole.log(message.content[0].text);`;
    else if(wire==='responses')code=`import OpenAI from "openai";\n\nconst client = new OpenAI({ apiKey: process.env.AIGW_API_KEY, baseURL: "${base}/v1" });\nconst response = await client.responses.create({ model: "${selectedModel}", input: "Say hello in one sentence." });\nconsole.log(response.output_text);`;
    else if(wire==='embeddings')code=`import OpenAI from "openai";\n\nconst client = new OpenAI({ apiKey: process.env.AIGW_API_KEY, baseURL: "${base}/v1" });\nconst response = await client.embeddings.create({ model: "${selectedModel}", input: "AIGW semantic search" });\nconsole.log(response.data[0].embedding.length);`;
    else code=`import OpenAI from "openai";\n\nconst client = new OpenAI({ apiKey: process.env.AIGW_API_KEY, baseURL: "${base}/v1" });\nconst response = await client.chat.completions.create({ model: "${selectedModel}", messages: [{ role: "user", content: "Say hello in one sentence." }] });\nconsole.log(response.choices[0].message.content);`;
  }
  $('#quickstart-code-block code').textContent=code;
}
function playgroundBody(wire,model,prompt,maxOutput) {
  if(wire==='messages')return {model,max_tokens:maxOutput,messages:[{role:'user',content:prompt}]};
  if(wire==='responses')return {model,input:prompt,max_output_tokens:maxOutput,stream:false};
  if(wire==='embeddings')return {model,input:prompt};
  return {model,messages:[{role:'user',content:prompt}],max_tokens:maxOutput,stream:false};
}
function playgroundTokenCount(payload) {
  const usage=payload?.usage;if(!usage)return 0;
  return Number(usage.total_tokens ?? (Number(usage.input_tokens||usage.prompt_tokens||0)+Number(usage.output_tokens||usage.completion_tokens||0)));
}
function playgroundDiagnosis(status,network=false) {
  if(network)return {title:'Gateway unreachable',message:'Verify the inference public URL, TLS certificate, and exact admin Origin allowed by the gateway.',action:'Open API keys',target:'keys'};
  if(status===400)return {title:'Request rejected',message:'Check the selected protocol, prompt, and output limit against the model details.',action:'Review model',target:'catalog'};
  if(status===401||status===403)return {title:'API key not authorized',message:'Use an active inference key and check its expiry, scopes, model allowlist, and monthly spend cap.',action:'Open API keys',target:'keys'};
  if(status===402)return {title:'Balance required',message:'Add funds to the prepaid wallet, then retry this request. Automatic top-up can prevent future interruptions.',action:'Add funds',target:'billing'};
  if(status===404)return {title:'Model route unavailable',message:'The model may be unavailable to this key or may not support the selected protocol.',action:'Choose a model',target:'catalog'};
  if(status===409)return {title:'Request conflict',message:'Retry with a new request after the in-flight billing reservation or account change completes.',action:'View usage',target:'usage'};
  if(status===429)return {title:'Limit reached',message:'Review request, token, concurrency, project, and API key limits before retrying.',action:'Review limits',target:'limits'};
  if(status>=500)return {title:'Provider unavailable',message:'The gateway could not complete the upstream request after routing and failover. Keep the request ID for support.',action:'Choose another model',target:'catalog'};
  return {title:'Request failed',message:'Review the response body and request ID. The request was not accepted as successful.',action:'View usage',target:'usage'};
}
function hidePlaygroundDiagnostic(){const node=$('#playground-diagnostic');node.classList.add('hidden');$('#playground-diagnostic-action').classList.add('hidden');}
function showPlaygroundDiagnostic(status,network=false) {
  const diagnosis=playgroundDiagnosis(status,network);const action=$('#playground-diagnostic-action');
  $('#playground-diagnostic-title').textContent=diagnosis.title;$('#playground-diagnostic-message').textContent=diagnosis.message;
  action.textContent=diagnosis.action;action.dataset.target=diagnosis.target;action.classList.toggle('hidden',!$(`.tab[data-section="${diagnosis.target}"]`)||$(`.tab[data-section="${diagnosis.target}"]`).classList.contains('hidden'));
  $('#playground-diagnostic').classList.remove('hidden');
}
async function runPlayground(event) {
  event.preventDefault();
  const key=$('#playground-key').value.trim();const model=modelSelector(selectedPlaygroundModel(),'#playground-provider');const wire=$('#playground-protocol').value;const prompt=$('#playground-prompt').value.trim();const maxOutput=Number($('#playground-max-output').value);
  if(!key||!model||!prompt||(wire!=='embeddings'&&(!Number.isSafeInteger(maxOutput)||maxOutput<1)))return toast('Complete the API request fields',true);
  state.playgroundKey=key;
  const endpoint=quickstartEndpoint(wire);const headers={'Content-Type':'application/json'};
  if(wire==='messages'){headers['X-API-Key']=key;headers['Anthropic-Version']='2023-06-01';}else headers.Authorization=`Bearer ${key}`;
  const result=$('#playground-result');const status=$('#playground-status');const send=$('#playground-send');const stop=$('#playground-stop');
  result.classList.remove('hidden');hidePlaygroundDiagnostic();status.className='badge';status.textContent='Sending';$('#playground-request-id').textContent='';$('#playground-duration').textContent='';$('#playground-tokens').textContent='';$('#playground-response').textContent='';
  send.disabled=true;stop.classList.remove('hidden');const controller=new AbortController();state.playgroundController=controller;const started=performance.now();
  try {
    const response=await fetch(endpoint,{method:'POST',mode:'cors',credentials:'omit',cache:'no-store',headers,body:JSON.stringify(playgroundBody(wire,model,prompt,maxOutput)),signal:controller.signal});
    const raw=await response.text();let payload;try{payload=raw?JSON.parse(raw):{};}catch{payload=raw;}
    const requestID=response.headers.get('X-AIGW-Request-ID')||payload?.error?.request_id||'';const duration=Math.round(performance.now()-started);const tokens=playgroundTokenCount(payload);
    status.className=`badge ${response.ok?'active':'suspended'}`;status.textContent=`${response.status} ${response.ok?'OK':'Error'}`;
    $('#playground-request-id').textContent=requestID?`Request ${requestID}`:'';$('#playground-duration').textContent=`${integer(duration)} ms`;$('#playground-tokens').textContent=tokens?`${integer(tokens)} tokens`:'';
    $('#playground-response').textContent=typeof payload==='string'?payload:JSON.stringify(payload,null,2);
    if(!response.ok){showPlaygroundDiagnostic(response.status);toast(payload?.error?.message||`Request failed (${response.status})`,true);}else{hidePlaygroundDiagnostic();toast('Request completed');setTimeout(()=>loadAll(),900);}
  } catch(error) {
    const stopped=error.name==='AbortError';status.className='badge suspended';status.textContent=stopped?'Stopped':'Network error';$('#playground-duration').textContent=`${integer(Math.round(performance.now()-started))} ms`;$('#playground-response').textContent=stopped?'Request cancelled.':error.message;if(stopped)hidePlaygroundDiagnostic();else showPlaygroundDiagnostic(0,true);toast(stopped?'Request stopped':error.message,true);
  } finally {
    if(state.playgroundController===controller)state.playgroundController=null;send.disabled=false;stop.classList.add('hidden');
  }
}
function renderCatalog() {
  const ownerSelect=$('#catalog-owner');const ownerValue=ownerSelect?.value||'';const owners=[...new Set(state.developerModels.map(item=>String(item.owned_by||'').trim()).filter(Boolean))].sort((a,b)=>a.localeCompare(b));
  if(ownerSelect){ownerSelect.innerHTML=`<option value="">All developers</option>${owners.map(owner=>`<option value="${esc(owner)}">${esc(owner)}</option>`).join('')}`;if(owners.includes(ownerValue))ownerSelect.value=ownerValue;}
  const search=String($('#catalog-search')?.value||'').trim().toLowerCase(); const protocol=$('#catalog-protocol')?.value||''; const input=$('#catalog-input')?.value||'';const owner=ownerSelect?.value||'';const sort=$('#catalog-sort')?.value||'newest';
  const models=state.developerModels.filter(item=>{const hay=[item.public_id,item.display_name,item.owned_by,item.description,...(item.capabilities||[])].join(' ').toLowerCase();return (!search||hay.includes(search))&&(!protocol||(item.supported_wire_apis||[]).includes(protocol))&&(!input||(item.input_modalities||[]).includes(input))&&(!owner||item.owned_by===owner);});
  const compareText=(a,b)=>String(a.display_name||a.public_id).localeCompare(String(b.display_name||b.public_id));
  models.sort((a,b)=>sort==='name'?compareText(a,b):sort==='input_price'?Number(a.input_price_micros_per_million||0)-Number(b.input_price_micros_per_million||0)||compareText(a,b):sort==='output_price'?Number(a.output_price_micros_per_million||0)-Number(b.output_price_micros_per_million||0)||compareText(a,b):sort==='context'?Number(b.context_window||0)-Number(a.context_window||0)||compareText(a,b):new Date(b.released_at||0)-new Date(a.released_at||0)||compareText(a,b));
  $('#catalog-count').textContent=`${models.length} of ${state.developerModels.length} models`;
  $('#catalog-grid').innerHTML=models.map(item=>{const health=providerHealthSummary(item);return `<article class="catalog-card"><div><span class="eyebrow">${esc(item.owned_by||'MODEL')}</span><h2>${esc(item.display_name||item.public_id)}</h2><div class="model-id">${esc(item.public_id)}</div></div><div class="provider-summary"><span class="badge ${health.status==='online'?'active':health.status}">${esc(health.label)}</span><small>${esc(health.detail)}</small></div><p>${esc(item.description||'No description provided.')}</p><div class="catalog-meta">${(item.supported_wire_apis||[]).map(apiName=>`<span class="tag">${esc(apiName)}</span>`).join('')}${(item.input_modalities||[]).map(modality=>`<span class="tag">${esc(modality)} input</span>`).join('')}</div><div class="catalog-price">${money(item.input_price_micros_per_million,item.price_currency)} in · ${money(item.output_price_micros_per_million,item.price_currency)} out / 1M tokens</div><small class="muted">${integer(item.context_window)} context · ${integer(item.max_output_tokens)} max output</small><div class="catalog-actions"><button type="button" class="button subtle" data-model-details="${esc(item.public_id)}">Details</button><button type="button" class="button secondary" data-use-model="${esc(item.public_id)}" ${modelIsUnavailable(item)?'disabled':''}>Use this model</button></div></article>`;}).join('')||'<div class="panel empty">No models match these filters.</div>';
}
function modelProtocolLabel(value) { return value==='chat_completions'?'OpenAI Chat Completions':value==='responses'?'OpenAI Responses':value==='embeddings'?'OpenAI Embeddings':value==='messages'?'Anthropic Messages':value; }
function showModelDetails(publicID) {
  const model=state.developerModels.find(item=>item.public_id===publicID);if(!model)return;
  state.detailModel=model;$('#model-dialog-title').textContent=model.display_name||model.public_id;$('#model-dialog-id').textContent=model.public_id;
  const lifecycle=model.lifecycle||'active';const status=lifecycle==='retired'?'retired':lifecycle==='deprecated'?'deprecated':lifecycle==='preview'?'preview':'available';
  const health=providerHealthSummary(model);const rows=[['Status',status],['Runtime',health.label],['Providers',health.detail],['Owner',model.owned_by||'—'],['Protocols',(model.supported_wire_apis||[]).map(modelProtocolLabel).join(', ')||'—'],['Input',(model.input_modalities||[]).join(', ')||'—'],['Output',(model.output_modalities||[]).join(', ')||'—'],['Context',`${integer(model.context_window)} tokens`],['Max output',`${integer(model.max_output_tokens)} tokens`],['Released',date(model.released_at)],['Capabilities',(model.capabilities||[]).join(', ')||'—'],['Aliases',(model.aliases||[]).join(', ')||'—']];
  $('#model-detail-grid').innerHTML=rows.map(([label,value])=>`<div class="model-detail-row"><span>${esc(label)}</span><strong>${esc(value)}</strong></div>`).join('');
  const providers=model.providers||[];$('#model-provider-health').classList.toggle('hidden',providers.length===0);
  const activeProbeCount=providers.reduce((sum,item)=>sum+Number(item.active_probes||0),0);
  const sharedCount=providers.reduce((sum,item)=>sum+Number(item.shared_attempts||0)+Number(item.shared_ttft_samples||0),0);
  $('#model-provider-health .section-heading small').textContent=sharedCount>0?'Measured from requests shared across gateway instances.':activeProbeCount>0?'Measured from requests and authenticated availability probes on this gateway instance.':'Measured from requests handled by this gateway instance.';
  $('#model-provider-health-body').innerHTML=providers.map(item=>{const measured=Number(item.recent_samples||0)>0;const availability=measured?`${Number(item.availability_percent||0).toFixed(1)}%`:'Not measured';const ttft=Number(item.ttft_ewma_ms||0)>0?`${integer(item.ttft_ewma_ms)} ms`:'Not measured';const latency=Number(item.header_latency_ewma_ms||0)>0?`${integer(item.header_latency_ewma_ms)} ms`:'Not measured';const retry=item.circuit_open_until?`Retry ${date(item.circuit_open_until)}`:'';const probes=Number(item.active_probes||0)>0?` · ${integer(item.active_probes)} probes`:'';const shared=Number(item.shared_attempts||0)>0?` · ${integer(item.shared_attempts)} shared`:'';return `<tr><td><strong>${esc(item.name||item.slug)}</strong><small class="price-line">${esc(item.slug)} · ${esc(modelProtocolLabel(item.wire_api)||item.protocol||'')}</small></td><td><span class="badge ${item.state==='healthy'?'active':item.state==='open'?'suspended':item.state==='degraded'?'degraded':''}">${esc(item.state==='open'?'Circuit open':item.state)}</span>${retry?`<small class="provider-retry">${esc(retry)}</small>`:''}</td><td>${esc(availability)}</td><td>${esc(ttft)}</td><td>${esc(latency)}</td><td>${integer(item.attempts||0)}${esc(shared)}${esc(probes)}</td></tr>`;}).join('');
  $('#model-dialog-playground').disabled=modelIsUnavailable(model);$('#estimate-input').value='1000';$('#estimate-output').value='500';$('#estimate-cache-read').value='0';$('#estimate-cache-write').value='0';renderModelEstimate();$('#model-dialog').showModal();
}
function renderModelEstimate() {
  const model=state.detailModel;if(!model)return;const input=Math.max(0,Number($('#estimate-input').value||0));const output=Math.max(0,Number($('#estimate-output').value||0));const cacheRead=Math.max(0,Number($('#estimate-cache-read').value||0));const cacheWrite=Math.max(0,Number($('#estimate-cache-write').value||0));const micros=(input*Number(model.input_price_micros_per_million||0)+output*Number(model.output_price_micros_per_million||0)+cacheRead*Number(model.cache_read_price_micros_per_million||0)+cacheWrite*Number(model.cache_write_price_micros_per_million||0))/1_000_000;$('#model-estimate').textContent=`Estimated cost ${money(Math.round(micros),model.price_currency||state.overview.billing_currency||'usd')}`;
}
function useModelInPlayground(publicID) { const model=state.developerModels.find(item=>item.public_id===publicID);if(!model||modelIsUnavailable(model)){toast('This model has no provider currently accepting traffic',true);return;}$('#quickstart-model').value=publicID;$('#playground-model').value=publicID;syncQuickstartProtocols();syncPlaygroundProtocols();$('#quickstart-provider').value='';$('#playground-provider').value='';renderQuickstartCode();if($('#model-dialog').open)$('#model-dialog').close();goTo('quickstart'); }
function renderTenants() {
  $('#tenants-body').innerHTML = state.tenants.map(item => `<tr><td><strong>${esc(item.name)}</strong></td><td><code>${esc(item.slug)}</code></td><td><span class="badge ${item.status}">${esc(item.status)}</span></td><td>${date(item.created_at)}</td></tr>`).join('') || emptyRow(4);
  ['project-tenant','key-tenant','topup-tenant','adjustment-tenant','user-tenant'].forEach(id => { const node=$(`#${id}`); if (node) node.innerHTML=selectOptions(state.tenants,'id','name', state.actor.tenant_id ? 'Current tenant' : 'Select tenant…'); });
  if (state.actor.tenant_id) ['project-tenant','key-tenant','topup-tenant','adjustment-tenant','user-tenant'].forEach(id => { const node=$(`#${id}`); if (node) node.value=state.actor.tenant_id; });
}
function renderProjects() { $('#projects-body').innerHTML = state.projects.map(item => `<tr><td><strong>${esc(item.name)}</strong></td><td><code>${shortID(item.tenant_id)}</code></td><td>${esc(item.slug)}</td><td><span class="badge ${item.status}">${esc(item.status)}</span></td></tr>`).join('') || emptyRow(4); renderKeyProjects(); }
function renderKeyProjects() { const tenant=$('#key-tenant').value;const node=$('#key-project');const selected=node.value;const projects=state.projects.filter(item=>!tenant||item.tenant_id===tenant);node.innerHTML=selectOptions(projects,'id','name');if(projects.some(item=>item.id===selected))node.value=selected;else if(projects.length===1)node.value=projects[0].id; }
function renderKeys() {
  const picker=$('#key-models');const selected=new Set([...picker.selectedOptions].map(option=>option.value));picker.innerHTML=state.developerModels.map(item=>`<option value="${esc(item.public_id)}">${esc(item.display_name||item.public_id)} (${esc(item.public_id)})</option>`).join('');[...picker.options].forEach(option=>{option.selected=selected.has(option.value);});
  $('#keys-body').innerHTML = state.keys.map(item => {
	const models=item.allowed_models||[];const expires=item.expires_at?date(item.expires_at):'Never';const tags=(item.tags||[]).map(tag=>`<span class="tag">${esc(tag)}</span>`).join('');
	const spent=Number(item.current_month_spend_micros||0),reserved=Number(item.current_month_reserved_micros||0),cap=Number(item.monthly_spend_micros||0),remaining=Math.max(0,cap-spent-reserved);
	const daySpent=Number(item.current_day_spend_micros||0),dayReserved=Number(item.current_day_reserved_micros||0),dayCap=Number(item.daily_spend_micros||0),dayRemaining=Math.max(0,dayCap-daySpent-dayReserved);
	const effectiveStatus=item.status==='active'&&item.expires_at&&new Date(item.expires_at)<=new Date()?'expired':item.status;
	const actions=can('keys.write')&&item.status!=='revoked'?`${item.status==='active'?`<button class="text-button" data-disable-key="${esc(item.id)}">Disable</button>`:`<button class="text-button" data-enable-key="${esc(item.id)}">Enable</button>`} <button class="text-button" data-rotate-key="${esc(item.id)}">Rotate</button> <button class="text-button danger" data-revoke-key="${esc(item.id)}">Revoke</button>`:'';
	return `<tr><td><strong>${esc(item.name)}</strong>${tags?`<small class="key-tags">${tags}</small>`:''}</td><td><code>${esc(apiKeyDisplay(item))}</code></td><td><code>${shortID(item.project_id)}</code></td><td>${(item.scopes||[]).map(scope=>`<span class="tag">${esc(scope)}</span>`).join('')}<small class="price-line">${models.length?`${integer(models.length)} selected model${models.length===1?'':'s'}`:'All visible models'}</small></td><td><strong>${money(spent)} this month</strong><small class="price-line">${integer(item.current_month_requests)} requests · ${money(reserved)} reserved</small><small class="price-line">${money(daySpent)} today · ${integer(item.current_day_requests)} requests</small></td><td>${cap?`${money(remaining)} monthly left`:'Monthly unlimited'}<small class="price-line">${dayCap?`${money(dayRemaining)} daily left`:'Daily unlimited'}</small><small class="price-line">${item.requests_per_minute?`${integer(item.requests_per_minute)} RPM`:'RPM unlimited'} · ${item.tokens_per_minute?`${integer(item.tokens_per_minute)} TPM`:'TPM unlimited'} · Expires: ${esc(expires)}</small></td><td><span class="badge ${effectiveStatus}">${esc(effectiveStatus)}</span><small class="price-line">Last used: ${esc(date(item.last_used_at))}</small></td><td>${actions}</td></tr>`;
  }).join('') || emptyRow(8);
}
function renderProviders() { $('#providers-body').innerHTML = state.providers.map(item => `<tr><td><strong>${esc(item.name)}</strong><small class="price-line"><code>${esc(item.slug)}</code></small></td><td><span class="tag">${esc(item.protocol)}</span><small class="price-line">${esc(item.wire_api)}</small></td><td class="truncate">${esc(item.base_url)}</td><td>${integer(item.route_count)}</td><td><span class="badge ${item.enabled?'active':'suspended'}">${item.enabled?'enabled':'disabled'}</span></td><td>${can('platform.write')?`<button class="text-button" data-toggle-provider="${esc(item.id)}" data-enabled="${!item.enabled}">${item.enabled?'Disable':'Enable'}</button>`:''}</td></tr>`).join('') || emptyRow(6); }
function renderModels() { $('#models-body').innerHTML = state.models.map(item => `<tr><td><strong>${esc(item.public_id)}</strong><small class="price-line">${esc(item.display_name||'')} · ${integer(item.context_window)} ctx · ${esc((item.input_modalities||[]).join('+'))} → ${esc((item.output_modalities||[]).join('+'))}</small></td><td>${esc(item.owned_by||'—')}<small class="price-line">v${item.price_version||1} ${esc(item.price_currency||'usd')} · in ${money(item.input_price_micros_per_million,item.price_currency)}/1M · out ${money(item.output_price_micros_per_million,item.price_currency)}/1M</small></td><td><div class="route-list">${(item.routes||[]).map(route=>`<span>${esc(route.provider_name||route.provider_id).slice(0,24)} → ${esc(route.upstream_model)} <em>p${route.priority} / w${route.weight}</em></span>`).join('')}</div></td><td><span class="badge ${item.enabled&&item.lifecycle!=='retired'?'active':'suspended'}">${esc(item.lifecycle||'active')}</span></td><td>${can('platform.write')?`<button class="text-button" data-toggle-model="${esc(item.id)}" data-enabled="${!item.enabled}">${item.enabled?'Disable':'Enable'}</button>`:''}</td></tr>`).join('') || emptyRow(5); }
function renderBilling() {
  $('#billing-currency').textContent=(state.overview.billing_currency||'').toUpperCase();
  $('#billing-accounts-body').innerHTML=state.billingAccounts.map(item=>`<tr><td><strong>${esc(item.tenant_name)}</strong><br><code>${shortID(item.tenant_id)}</code></td><td>${money(item.balance_micros,item.currency)}</td><td>${money(item.reserved_micros,item.currency)}</td><td><strong>${money(item.available_micros,item.currency)}</strong></td><td>${date(item.updated_at)}</td></tr>`).join('')||emptyRow(5);
  $('#billing-ledger-body').innerHTML=state.ledger.map(item=>{const requestReference=item.kind==='usage'&&item.source_type==='request'&&item.source_id;const reference=requestReference?`<button class="text-button ledger-request-link" type="button" data-ledger-request="${esc(item.source_id)}" data-ledger-tenant="${esc(item.tenant_id)}" title="View request ${esc(item.source_id)}"><code>${shortID(item.source_id)}</code></button>`:`<code title="${esc(item.source_id)}">${shortID(item.source_id)}</code>`;return `<tr><td>${date(item.created_at)}</td><td><code>${shortID(item.tenant_id)}</code></td><td><span class="tag">${esc(item.kind)}</span></td><td class="${item.amount_micros>=0?'money-positive':'money-negative'}">${money(item.amount_micros,item.currency)}</td><td>${money(item.balance_after_micros,item.currency)}</td><td title="${esc(item.description)}">${reference}</td></tr>`;}).join('')||emptyRow(6);
  const orders=state.orders||[];$('#billing-orders-body').innerHTML=orders.map(item=>`<tr><td>${date(item.created_at)}${item.trigger_type==='auto'?'<small class="price-line">automatic</small>':''}</td><td>${money(item.amount_micros,item.currency)}</td><td><span class="badge ${item.status==='paid'?'active':'suspended'}">${esc(item.status)}</span></td><td title="${esc(item.reconciliation_error||'')}"><span class="badge ${['ok','repaired','resolved'].includes(item.reconciliation_status)?'active':'suspended'}">${esc(item.reconciliation_status||'unknown')}</span></td><td>${item.invoice_url?`<a href="${esc(item.invoice_url)}" target="_blank" rel="noopener">Invoice</a>`:''} ${item.invoice_pdf_url?`<a href="${esc(item.invoice_pdf_url)}" target="_blank" rel="noopener">PDF</a>`:''} ${item.receipt_url?`<a href="${esc(item.receipt_url)}" target="_blank" rel="noopener">Receipt</a>`:''}</td><td>${item.trigger_type!=='auto'&&['failed','expired'].includes(item.status)?`<button class="text-button" data-retry-order="${esc(item.id)}">Retry</button>`:''}${can('billing.adjust')&&item.status==='pending'&&item.reconciliation_status==='missing'?` <button class="text-button danger" data-resolve-order="${esc(item.id)}" data-order-tenant="${esc(item.tenant_id)}">Resolve</button>`:''}${can('billing.adjust')&&item.status==='paid'&&item.reconciliation_status==='missing'&&!item.stripe_payment_intent_id?` <button class="text-button danger" data-reverse-order="${esc(item.id)}" data-order-tenant="${esc(item.tenant_id)}">Reverse</button>`:''}${can('billing.adjust')&&['paid','partially_refunded'].includes(item.status)?` <button class="text-button danger" data-refund-order="${esc(item.id)}" data-order-amount="${item.amount_minor}">Refund</button>`:''}</td></tr>`).join('')||emptyRow(6);
  $('#refunds-body').innerHTML=(state.refunds||[]).map(item=>`<tr><td>${date(item.created_at)}</td><td><code>${shortID(item.topup_order_id)}</code></td><td>${money(item.amount_micros,item.currency)}</td><td><span class="badge ${item.status==='succeeded'?'active':'suspended'}">${esc(item.status)}</span></td><td>${esc(item.last_error||'—')}</td></tr>`).join('')||emptyRow(5);
  $('#disputes-body').innerHTML=(state.disputes||[]).map(item=>`<tr><td>${date(item.updated_at)}</td><td>${money(item.amount_micros,item.currency)}</td><td><span class="badge ${item.status==='won'?'active':'suspended'}">${esc(item.status)}</span></td><td>${esc(item.reason)}</td><td>${date(item.due_by)}</td></tr>`).join('')||emptyRow(5);
  renderBillingProfile();
  renderAutoTopUp();
}
function renderBillingProfile(){
  if(!state.actor.tenant_id)return;const item=state.billingProfile||{};const form=$('#billing-profile-form');
  for(const name of ['legal_name','billing_email','address_line1','address_line2','city','region','postal_code','country'])form.elements[name].value=item[name]||'';
  const labels={not_configured:'Not configured',checkout_managed:'Stripe managed',disabled:'Saved locally',pending:'Syncing',synced:'Stripe synced',failed:'Sync needed'};const status=$('#billing-profile-status');const label=labels[item.stripe_sync_status]||'Not configured';status.textContent=label;status.className=`badge ${item.stripe_sync_status==='synced'?'active':item.stripe_sync_status==='failed'?'suspended':''}`;
  $('#billing-profile-error').textContent=item.stripe_sync_error||'';form.querySelector('button[type=submit]').disabled=!can('billing.topup');
  $('#billing-portal').disabled=!item.stripe_customer_configured;
}
function renderAutoTopUp(){
  const item=state.autoTopUp||{};if(!state.actor.tenant_id)return;
  const currency=item.currency||state.overview.billing_currency||'usd';const digits=currencyDigits(currency);
  $('#auto-topup-enabled').checked=Boolean(item.enabled);$('#auto-topup-threshold').value=scaledToDecimal(item.threshold_micros||0,6);$('#auto-topup-amount').value=scaledToDecimal(item.topup_amount_minor||0,digits);
  const status=$('#auto-topup-status');status.textContent=String(item.status||'not_configured').replaceAll('_',' ');status.className=`badge ${item.enabled&&item.status==='ready'?'active':item.status==='action_required'||item.status==='failed'?'suspended':''}`;
  $('#auto-topup-payment-method').textContent=item.payment_method_configured?`${item.payment_method_brand||item.payment_method_type||'payment method'} •••• ${item.payment_method_last4||''}${item.payment_method_exp_month?` · ${String(item.payment_method_exp_month).padStart(2,'0')}/${item.payment_method_exp_year}`:''}`:'Not saved';
  $('#auto-topup-error').textContent=item.last_error||'';
  const writable=can('billing.topup');$$('#auto-topup-form input').forEach(node=>node.disabled=!writable);$('#auto-topup-form button[type=submit]').disabled=!writable;$('#auto-topup-payment-setup').disabled=!writable||!item.stripe_enabled;
  $('#auto-topup-payment-setup').textContent=item.payment_method_configured?'Replace payment method':'Save payment method';
}
function renderSecurity() {
  const totp=Boolean(state.mfa?.totp_enabled);
	$$('#account [autocomplete="username"]').forEach(node=>{node.value=state.actor.email||'';});
  $('#totp-action').textContent=totp?'Disable TOTP':'Set up TOTP';
  $('#totp-disable-code').classList.toggle('hidden',!totp);
  $('#totp-disable-code input').required=totp;
  $('#sessions-body').innerHTML=(state.sessions||[]).map(item=>`<tr><td><strong>${esc(item.current?'Current device':'Browser session')}</strong><br><span class="muted">${esc(item.user_agent||'Unknown client')}</span></td><td><code>${esc(item.remote_ip||'—')}</code></td><td><span class="tag">${esc(item.auth_method)}</span></td><td>${date(item.last_seen_at)}</td><td><button class="text-button danger" data-revoke-session="${esc(item.id)}">Sign out</button></td></tr>`).join('')||emptyRow(5);
  $('#passkeys-body').innerHTML=(state.mfa?.passkeys||[]).map(item=>`<tr><td><strong>${esc(item.name)}</strong></td><td>${date(item.created_at)}</td><td>${date(item.last_used_at)}</td><td><button class="text-button danger" data-delete-passkey="${esc(item.id)}">Delete</button></td></tr>`).join('')||emptyRow(4);
  $('#orders-body').innerHTML=(state.orders||[]).map(item=>`<tr><td>${date(item.created_at)}</td><td>${money(item.amount_micros,item.currency)}</td><td><span class="badge ${item.status==='paid'?'active':'suspended'}">${esc(item.status)}</span></td><td><code>${shortID(item.id)}</code></td></tr>`).join('')||emptyRow(4);
}
function renderUsage() {
  const platformDiagnostics=!state.actor.tenant_id&&can('platform.read');
  const project=$('#usage-project'),key=$('#usage-key'),model=$('#usage-model'),provider=$('#usage-provider');
  const projectValue=project.value,keyValue=key.value,modelValue=model.value,providerValue=provider.value;
  const providers=new Map();if(platformDiagnostics)state.developerModels.forEach(item=>(item.providers||[]).forEach(route=>providers.set(route.slug,route.name||route.slug)));
  project.innerHTML=`<option value="">All projects</option>${state.projects.map(item=>`<option value="${esc(item.id)}">${esc(item.name)}</option>`).join('')}`;
  key.innerHTML=`<option value="">All API keys</option>${state.keys.map(item=>`<option value="${esc(item.id)}">${esc(item.name)} (${esc(apiKeyDisplay(item))})</option>`).join('')}`;
  model.innerHTML=`<option value="">All models</option>${state.developerModels.map(item=>`<option value="${esc(item.public_id)}">${esc(item.display_name||item.public_id)}</option>`).join('')}`;
  provider.innerHTML=`<option value="">All providers</option>${[...providers].sort((a,b)=>a[1].localeCompare(b[1])).map(([slug,name])=>`<option value="${esc(slug)}">${esc(name)} (${esc(slug)})</option>`).join('')}`;
  if(projectValue)project.value=projectValue;if(keyValue)key.value=keyValue;if(modelValue)model.value=modelValue;if(providerValue&&platformDiagnostics)provider.value=providerValue;
  $('#usage-provider-filter').classList.toggle('hidden',!platformDiagnostics);$('#usage-provider-panel').classList.toggle('hidden',!platformDiagnostics);
  if(!$('#usage-from').value){const query=new URLSearchParams(defaultUsageQuery());$('#usage-from').value=query.get('from');$('#usage-to').value=query.get('to');}
  const points=state.usageDaily||[];const totals=points.reduce((acc,item)=>{acc.requests+=Number(item.request_count||0);acc.success+=Number(item.successful_requests||0);acc.tokens+=Number(item.total_tokens||0);acc.charged+=Number(item.charged_micros||0);acc.duration+=Number(item.average_duration_ms||0)*Number(item.request_count||0);acc.p50=Math.max(acc.p50,Number(item.p50_duration_ms||0));acc.p95=Math.max(acc.p95,Number(item.p95_duration_ms||0));acc.ttftP95=Math.max(acc.ttftP95,Number(item.p95_ttft_ms||0));return acc;},{requests:0,success:0,tokens:0,charged:0,duration:0,p50:0,p95:0,ttftP95:0});
  const metrics=[['Requests',integer(totals.requests),'selected range'],['Success rate',percent(totals.success,totals.requests),'completed requests'],['Tokens',integer(totals.tokens),'input and output'],['Charged',money(totals.charged),'wallet debit'],['Average latency',totals.requests?`${integer(Math.round(totals.duration/totals.requests))} ms`:'—','request weighted'],['P50 latency',totals.p50?`${integer(totals.p50)} ms`:'—','highest daily median'],['P95 latency',totals.p95?`${integer(totals.p95)} ms`:'—','highest daily P95'],['P95 TTFT',totals.ttftP95?`${integer(totals.ttftP95)} ms`:'—','highest daily P95']];$('#usage-metrics').innerHTML=metrics.map(([label,value,sub])=>`<article class="metric"><span>${label}</span><strong>${esc(value)}</strong><small>${esc(sub)}</small></article>`).join('');
  const maxRequests=Math.max(1,...points.map(item=>Number(item.request_count||0)));$('#usage-chart').innerHTML=points.length?`<div class="chart-bars">${points.map(item=>`<div class="chart-day" title="${esc(new Date(item.day).toLocaleDateString())}: ${integer(item.request_count)} requests, ${percent(item.successful_requests,item.request_count)} success, ${money(item.charged_micros)} charged"><div class="chart-bar"><span class="${chartHeightClass(item.request_count,maxRequests)}"></span></div><small>${new Date(item.day).toLocaleDateString(undefined,{month:'short',day:'numeric'})}</small></div>`).join('')}</div>`:'<div class="empty">No usage in this range</div>';
  $('#usage-summary-body').innerHTML=state.usageSummary.map(item=>`<tr><td>${new Date(item.period_start).toLocaleDateString(undefined,{year:'numeric',month:'short'})}</td><td><strong>${esc(item.project_name)}</strong></td><td>${integer(item.request_count)}</td><td>${percent(item.successful_requests,item.request_count)}</td><td>${integer(item.input_tokens)}</td><td>${integer(item.output_tokens)}</td><td>${money(item.cost_micros)}</td></tr>`).join('')||emptyRow(7);
  const analytics=Array.isArray(state.usageAnalytics)?{models:[],providers:[],keys:[]}:state.usageAnalytics||{models:[],providers:[],keys:[]};
  const changeLabel=item=>item.charge_change_percent==null?(Number(item.previous_charged_micros||0)===0&&Number(item.charged_micros||0)>0?'New':'—'):`${Number(item.charge_change_percent)>=0?'+':''}${Number(item.charge_change_percent).toFixed(1)}%`;
  const changeClass=item=>item.charge_change_percent==null?(Number(item.charged_micros||0)>0?'positive':''):Number(item.charge_change_percent)>0?'money-negative':'positive';
  const cacheRate=item=>{const denominator=Number(item.input_tokens||0)+Number(item.cache_read_input_tokens||0)+Number(item.cache_creation_input_tokens||0);return denominator?percent(item.cache_read_input_tokens,denominator):'—';};
  const latencyPair=item=>`${integer(item.p50_duration_ms)} / ${integer(item.p95_duration_ms)} ms`;
  const ttftPair=item=>item.p95_ttft_ms?`${integer(item.p50_ttft_ms)} / ${integer(item.p95_ttft_ms)} ms`:'—';
  $('#usage-model-analytics-body').innerHTML=(analytics.models||[]).map(item=>`<tr><td><strong>${esc(item.public_model)}</strong>${platformDiagnostics?`<small class="price-line">${integer(item.provider_count)} provider${Number(item.provider_count)===1?'':'s'}</small>`:''}</td><td>${integer(item.request_count)}</td><td>${percent(item.successful_requests,item.request_count)}</td><td>${integer(item.total_tokens)}</td><td>${money(item.charged_micros)}</td><td class="${changeClass(item)}">${changeLabel(item)}</td><td>${latencyPair(item)}</td><td>${ttftPair(item)}</td><td>${integer(item.missing_usage_requests)}</td></tr>`).join('')||emptyRow(9);
  $('#usage-key-analytics-body').innerHTML=(analytics.keys||[]).map(item=>`<tr><td><strong>${esc(item.key_name)}</strong><small class="price-line">${shortID(item.key_id)}</small></td><td>${integer(item.request_count)}</td><td>${percent(item.successful_requests,item.request_count)}</td><td>${integer(item.model_count)}</td><td>${integer(item.total_tokens)}</td><td>${money(item.charged_micros)}</td><td>${latencyPair(item)}</td><td>${ttftPair(item)}</td></tr>`).join('')||emptyRow(8);
  $('#usage-provider-analytics-body').innerHTML=(analytics.providers||[]).map(item=>`<tr><td><strong>${esc(item.provider_name)}</strong><small class="price-line">${esc(item.wire_api||'unknown')}</small></td><td>${integer(item.request_count)}</td><td>${percent(item.successful_requests,item.request_count)}</td><td>${integer(item.model_count)}</td><td>${cacheRate(item)}</td><td>${money(item.charged_micros)}</td><td class="${changeClass(item)}">${changeLabel(item)}</td><td>${latencyPair(item)}</td><td>${ttftPair(item)}</td></tr>`).join('')||emptyRow(9);
  $('#usage-events-body').innerHTML=state.usage.map((item,index)=>`<tr><td>${date(item.started_at)}</td><td><code title="${esc(item.request_id)}">${shortID(item.request_id)}</code><small class="price-line">${esc(item.protocol)}${item.attempts>1?` · ${item.attempts} attempts`:''}</small></td><td>${esc(item.project_name||shortID(item.project_id))}<small class="price-line">${esc(item.key_name||shortID(item.key_id))}</small></td><td>${esc(item.public_model)}${platformDiagnostics&&item.provider_name?`<small class="price-line">${esc(item.provider_name)}</small>`:''}</td><td><span class="badge ${item.success?'active':'suspended'}">${item.status_code}</span>${item.error_type?`<small class="error-label">${esc(item.error_type)}</small>`:''}<small class="price-line">${esc(item.metering_status||'')}</small></td><td>${integer(item.total_tokens)}</td><td>${money(item.charged_micros)}${item.uncollected_micros?`<small class="error-label">${money(item.uncollected_micros)} uncollected</small>`:''}</td><td>${integer(item.duration_ms)} ms<small class="price-line">TTFT ${item.ttft_ms?`${integer(item.ttft_ms)} ms`:'—'}</small></td><td><button class="text-button" type="button" data-usage-details="${index}">Details</button></td></tr>`).join('')||emptyRow(9);
  $('#usage-page-label').textContent=`Page ${state.usagePaging.history.length+1}`;$('#usage-page-prev').disabled=state.usagePaging.history.length===0;$('#usage-page-next').disabled=!state.usagePaging.nextCursor;
}
function usageDiagnostic(item) {
  const diagnostic={request_id:item.request_id,started_at:item.started_at,status_code:item.status_code,success:Boolean(item.success),error_type:item.error_type||'',project:{id:item.project_id,name:item.project_name||''},api_key:{id:item.key_id,name:item.key_name||''},model:{public_id:item.public_model},transport:{protocol:item.protocol,stream:Boolean(item.stream),attempts:Number(item.attempts||0),duration_ms:Number(item.duration_ms||0),ttft_ms:Number(item.ttft_ms||0)},usage:{input_tokens:Number(item.input_tokens||0),output_tokens:Number(item.output_tokens||0),total_tokens:Number(item.total_tokens||0),cache_read_input_tokens:Number(item.cache_read_input_tokens||0),cache_creation_input_tokens:Number(item.cache_creation_input_tokens||0),reported:Boolean(item.usage_reported)},billing:{cost_micros:Number(item.cost_micros||0),charged_micros:Number(item.charged_micros||0),uncollected_micros:Number(item.uncollected_micros||0),metering_status:item.metering_status||''}};
  if(!state.actor.tenant_id&&can('platform.read'))diagnostic.model.route={provider:item.provider_name||item.provider_id||'',upstream_id:item.upstream_model||''};return diagnostic;
}
function openUsageDetails(item) {
  if(!item)return;state.detailUsage=item;$('#usage-dialog-title').textContent=item.public_model||'Request details';$('#usage-dialog-id').textContent=item.request_id;
  const throughput=item.duration_ms>0&&item.output_tokens>0?`${(Number(item.output_tokens)*1000/Number(item.duration_ms)).toFixed(1)} output tokens/s`:'—';const rows=[['Time',date(item.started_at)],['Status',`${item.status_code} · ${item.success?'success':'error'}`],['Project',item.project_name||shortID(item.project_id)],['API key',item.key_name||shortID(item.key_id)],['Protocol',`${item.protocol}${item.stream?' · stream':''}`],['Public model',item.public_model],['Attempts',integer(item.attempts)],['Latency',`${integer(item.duration_ms)} ms`],['TTFT',item.ttft_ms?`${integer(item.ttft_ms)} ms`:'—'],['Throughput',throughput],['Tokens',`${integer(item.input_tokens)} in · ${integer(item.output_tokens)} out`],['Cache',`${integer(item.cache_read_input_tokens)} read · ${integer(item.cache_creation_input_tokens)} write`],['Charged',money(item.charged_micros)],['Metering',item.metering_status||'—'],['Error',item.error_type||'—']];
  if(!state.actor.tenant_id&&can('platform.read'))rows.splice(6,0,['Provider',item.provider_name||item.provider_id||'—'],['Upstream model',item.upstream_model||'—']);
  $('#usage-detail-grid').innerHTML=rows.map(([label,value])=>`<div class="model-detail-row"><span>${esc(label)}</span><strong>${esc(value)}</strong></div>`).join('');$('#usage-diagnostic').textContent=JSON.stringify(usageDiagnostic(item),null,2);const releaseForm=$('#release-reservation-form');releaseForm.classList.toggle('hidden',!can('billing.adjust')||item.metering_status!=='missing');releaseForm.reset();$('#usage-dialog').showModal();
}
function showUsageDetails(index) { openUsageDetails(state.usage[Number(index)]); }
async function showLedgerRequest(requestID,tenantID) {
  const params=new URLSearchParams({request_id:requestID,limit:'1'});if(!state.actor.tenant_id&&tenantID)params.set('tenant_id',tenantID);
  const page=await api(`/usage?${params}`);const items=Array.isArray(page)?page:(page?.data||[]);if(!items.length)throw new Error('The request record is no longer available');openUsageDetails(items[0]);
}
function renderLimits() {
  const existing=new Map(state.limits.map(item=>[item.project_id,item]));
  $('#limits-body').innerHTML=state.projects.map(project=>{const item=existing.get(project.id)||{};return `<tr data-limit-project="${esc(project.id)}"><td><strong>${esc(project.name)}</strong><br><code>${shortID(project.id)}</code></td><td><input class="limit-rpm" type="number" min="0" value="${item.requests_per_minute||0}" ${can('limits.write')?'':'disabled'}></td><td><input class="limit-tpm" type="number" min="0" value="${item.tokens_per_minute||0}" ${can('limits.write')?'':'disabled'}></td><td><input class="limit-concurrency" type="number" min="0" value="${item.concurrent_requests||0}" ${can('limits.write')?'':'disabled'}></td><td><input class="limit-spend" inputmode="decimal" value="${scaledToDecimal(item.monthly_spend_micros||0,6)}" ${can('limits.write')?'':'disabled'}></td><td>${can('limits.write')?'<button class="button secondary save-limit">Save</button>':''}</td></tr>`}).join('')||emptyRow(6);
}
function renderUsers() {
  const roles = state.actor.tenant_id ? [['tenant_admin','Tenant admin'],['tenant_billing','Billing'],['tenant_developer','Developer'],['tenant_viewer','Viewer']] : [['platform_admin','Platform admin'],['platform_viewer','Platform viewer'],['tenant_admin','Tenant admin'],['tenant_billing','Billing'],['tenant_developer','Developer'],['tenant_viewer','Viewer']];
  $('#user-role').innerHTML=roles.map(([value,label])=>`<option value="${value}">${label}</option>`).join('');
  $('#users-body').innerHTML=state.users.map(item=>`<tr><td><strong>${esc(item.display_name)}</strong><br><span class="muted">${esc(item.email)}</span></td><td><span class="tag">${esc(item.role.replaceAll('_',' '))}</span></td><td><code>${shortID(item.tenant_id)}</code></td><td>${item.email_verified?'<span class="badge active">verified</span>':'<span class="badge suspended">pending</span>'} ${(item.mfa_methods||[]).map(method=>`<span class="tag">${esc(method)}</span>`).join('')}</td><td>${date(item.last_used_at)}</td><td><span class="badge ${item.status}">${esc(item.status)}</span></td><td>${item.status!=='revoked'&&can('users.write')?`<button class="text-button danger" data-revoke-user="${esc(item.id)}">Revoke</button>`:''}</td></tr>`).join('')||emptyRow(7);
}
function renderAudit() { $('#audit-body').innerHTML=state.audit.map(item=>`<tr><td>${date(item.created_at)}</td><td><span class="tag">${esc(item.actor_role.replaceAll('_',' '))}</span></td><td>${esc(item.action)}</td><td><code>${esc(item.method)}</code></td><td><span class="badge ${item.status_code<400?'active':'suspended'}">${item.status_code}</span></td><td><code>${shortID(item.request_id)}</code></td><td><code>${esc(item.remote_ip||'—')}</code></td></tr>`).join('')||emptyRow(7); }
function renderRouteEditor() { const current=$('#route-editor');if(!current.children.length&&can('platform.write'))addRoute();$$('.route-provider').forEach(select=>{const selected=select.value;select.innerHTML=selectOptions(state.providers.filter(item=>item.enabled),'id','name','Provider…');select.value=selected;}); }
function addRoute() { const wrapper=document.createElement('div');wrapper.className='route-row';wrapper.innerHTML='<select class="route-provider" required></select><input class="route-upstream" required placeholder="Upstream model"><input class="route-priority" type="number" min="0" value="0" title="Priority"><input class="route-weight" type="number" min="1" max="100" value="100" title="Weight"><button class="icon-button remove-route" type="button" aria-label="Remove route">×</button>';$('#route-editor').appendChild(wrapper);renderRouteEditor(); }

document.addEventListener('click',async(event)=>{
  const authTab=event.target.closest('.auth-tab');if(authTab){$$('.auth-tab').forEach(node=>node.classList.toggle('active',node===authTab));$$('.auth-pane').forEach(node=>node.classList.toggle('active',node.id===authTab.dataset.authPane));authError();return;}
  const tab=event.target.closest('.tab');if(tab){$$('.tab').forEach(node=>node.classList.toggle('active',node===tab));$$('.section').forEach(node=>node.classList.toggle('active',node.id===tab.dataset.section));return;}
  const goto=event.target.closest('[data-goto]');if(goto){goTo(goto.dataset.goto);return;}
  const scroll=event.target.closest('[data-scroll-to]');if(scroll){document.getElementById(scroll.dataset.scrollTo)?.scrollIntoView({behavior:'smooth',block:'start'});return;}
  const endpointCopy=event.target.closest('[data-copy-endpoint]');if(endpointCopy){try{const values=JSON.parse($('#endpoint-list').dataset.values||'[]');await navigator.clipboard.writeText(values[Number(endpointCopy.dataset.copyEndpoint)]||'');toast('Endpoint copied');}catch(error){toast('Copy failed',true);}return;}
  const details=event.target.closest('[data-model-details]');if(details){showModelDetails(details.dataset.modelDetails);return;}
  const useModel=event.target.closest('[data-use-model]');if(useModel){useModelInPlayground(useModel.dataset.useModel);return;}
  const usageDetails=event.target.closest('[data-usage-details]');if(usageDetails){showUsageDetails(usageDetails.dataset.usageDetails);return;}
  const ledgerRequest=event.target.closest('[data-ledger-request]');if(ledgerRequest){try{await showLedgerRequest(ledgerRequest.dataset.ledgerRequest,ledgerRequest.dataset.ledgerTenant);}catch(error){toast(error.message,true);}return;}
  if(event.target.id==='reload'){try{await api('/reload',{method:'POST',body:'{}'});await loadAll();toast('Snapshot reloaded');}catch(error){toast(error.message,true);}}
  if(event.target.id==='add-route')addRoute();if(event.target.closest('.remove-route'))event.target.closest('.route-row').remove();
  const disableKey=event.target.closest('[data-disable-key]');if(disableKey&&confirm('Disable this API key immediately?')){try{await api(`/keys/${disableKey.dataset.disableKey}/disable`,{method:'POST',body:'{}'});await loadAll();toast('API key disabled');}catch(error){toast(error.message,true);}}
  const enableKey=event.target.closest('[data-enable-key]');if(enableKey){try{await api(`/keys/${enableKey.dataset.enableKey}/enable`,{method:'POST',body:'{}'});await loadAll();toast('API key enabled');}catch(error){toast(error.message,true);}}
  const rotateKey=event.target.closest('[data-rotate-key]');if(rotateKey&&confirm('Rotate this API key? The current secret will stop working immediately.')){try{const result=await api(`/keys/${rotateKey.dataset.rotateKey}/rotate`,{method:'POST',body:'{}'});state.playgroundKey=result.key;showSecret('API key rotated',result.key);await loadAll();toast(result.runtime_sync_status==='applied'?'API key rotated':'API key rotated; runtime reload is pending');}catch(error){toast(error.message,true);}}
  const revokeKey=event.target.closest('[data-revoke-key]');if(revokeKey&&confirm('Permanently revoke this API key?')){try{await api(`/keys/${revokeKey.dataset.revokeKey}/revoke`,{method:'POST',body:'{}'});await loadAll();toast('API key revoked');}catch(error){toast(error.message,true);}}
  const revokeUser=event.target.closest('[data-revoke-user]');if(revokeUser&&confirm('Revoke this console credential?')){try{await api(`/users/${revokeUser.dataset.revokeUser}/revoke`,{method:'POST',body:'{}'});await loadAll();toast('Console credential revoked');}catch(error){toast(error.message,true);}}
  const provider=event.target.closest('[data-toggle-provider]');if(provider){try{await api(`/providers/${provider.dataset.toggleProvider}/toggle`,{method:'POST',body:JSON.stringify({enabled:provider.dataset.enabled==='true'})});await loadAll();toast('Provider updated');}catch(error){toast(error.message,true);}}
  const model=event.target.closest('[data-toggle-model]');if(model){try{await api(`/models/${model.dataset.toggleModel}/toggle`,{method:'POST',body:JSON.stringify({enabled:model.dataset.enabled==='true'})});await loadAll();toast('Model updated');}catch(error){toast(error.message,true);}}
  const session=event.target.closest('[data-revoke-session]');if(session&&confirm('Sign out this device?')){try{const result=await api(`/auth/sessions/${session.dataset.revokeSession}/revoke`,{method:'POST',body:'{}'});if(result.current){state.csrf='';state.actor={};setConnected(false);}else{await loadAll();toast('Device signed out');}}catch(error){toast(error.message,true);}}
  const passkey=event.target.closest('[data-delete-passkey]');if(passkey&&confirm('Delete this passkey?')){const password=$('#passkey-form [name=current_password]').value;if(!password){toast('Enter your current password in the Passkey panel',true);}else{try{await api(`/auth/passkeys/${passkey.dataset.deletePasskey}/delete`,{method:'POST',body:JSON.stringify({current_password:password})});await loadAll();toast('Passkey deleted');}catch(error){toast(error.message,true);}}}
  const save=event.target.closest('.save-limit');if(save){const row=save.closest('[data-limit-project]');try{await api(`/limits/${row.dataset.limitProject}`,{method:'POST',body:JSON.stringify({requests_per_minute:Number(row.querySelector('.limit-rpm').value),tokens_per_minute:Number(row.querySelector('.limit-tpm').value),concurrent_requests:Number(row.querySelector('.limit-concurrency').value),monthly_spend_micros:decimalToScaled(row.querySelector('.limit-spend').value,6)})});await loadAll();toast('Project limits updated');}catch(error){toast(error.message,true);}}
  if(event.target.id==='billing-portal'){try{const result=await api('/billing/portal-sessions',{method:'POST',body:'{}'});window.location.assign(result.url);}catch(error){toast(error.message,true);}}
  if(event.target.id==='billing-export'){try{const headers={};if(state.token)headers.Authorization=`Bearer ${state.token}`;const response=await fetch('./api/billing/export.csv',{credentials:'same-origin',headers});if(!response.ok){const payload=await response.json().catch(()=>({}));throw new Error(payload?.error?.message||`Export failed (${response.status})`);}const blob=await response.blob();const url=URL.createObjectURL(blob);const link=document.createElement('a');link.href=url;link.download=`aigw-ledger-${new Date().toISOString().slice(0,10)}.csv`;document.body.appendChild(link);link.click();link.remove();URL.revokeObjectURL(url);toast('Financial export downloaded');}catch(error){toast(error.message,true);}}
  if(event.target.id==='billing-reconcile'){try{const result=await api('/billing/reconcile',{method:'POST',body:'{}'});toast(`Reconciliation ${result.status}: ${result.mismatch_count} mismatches`,result.mismatch_count>0);await loadAll();}catch(error){toast(error.message,true);}}
  const retryOrder=event.target.closest('[data-retry-order]');if(retryOrder){try{const result=await api(`/billing/orders/${retryOrder.dataset.retryOrder}/retry`,{method:'POST',body:'{}'});window.location.assign(result.url);}catch(error){toast(error.message,true);}}
  const resolveOrder=event.target.closest('[data-resolve-order]');if(resolveOrder){const reason=prompt('Resolution reason for this missing Stripe Session');if(reason){try{await api(`/billing/orders/${resolveOrder.dataset.resolveOrder}/resolve-missing?tenant_id=${encodeURIComponent(resolveOrder.dataset.orderTenant)}`,{method:'POST',body:JSON.stringify({reason})});await loadAll();toast('Missing top-up resolved');}catch(error){toast(error.message,true);}}}
  const reverseOrder=event.target.closest('[data-reverse-order]');if(reverseOrder){const reason=prompt('Reason for reversing this unverified local credit');if(reason&&confirm('Post an equal negative ledger entry for this credit?')){try{await api(`/billing/orders/${reverseOrder.dataset.reverseOrder}/reverse-missing-credit?tenant_id=${encodeURIComponent(reverseOrder.dataset.orderTenant)}`,{method:'POST',body:JSON.stringify({reason})});await loadAll();toast('Unverified credit reversed');}catch(error){toast(error.message,true);}}}
  const refundOrder=event.target.closest('[data-refund-order]');if(refundOrder){const amount=prompt('Refund amount in account currency');if(amount){try{const digits=currencyDigits(state.overview.billing_currency||'usd');await api(`/billing/orders/${refundOrder.dataset.refundOrder}/refund?tenant_id=${encodeURIComponent(state.orders.find(item=>item.id===refundOrder.dataset.refundOrder)?.tenant_id||'')}`,{method:'POST',body:JSON.stringify({amount_minor:decimalToScaled(amount,digits),reason:'requested_by_customer'})});await loadAll();toast('Refund queued');}catch(error){toast(error.message,true);}}}
});

$('#key-tenant').addEventListener('change',renderKeyProjects);
async function completeBrowserLogin(result,form){state.csrf=result.csrf_token||cookie('aigw_csrf');state.pendingMFA=null;if(form)form.reset();await loadAll(result);}
$('#login-pane').addEventListener('submit',async(event)=>{event.preventDefault();authError();try{state.token='';state.csrf='';const result=await api('/auth/login',{method:'POST',body:JSON.stringify(formJSON(event.target))});if(result.mfa_required){state.pendingMFA=result;$('#mfa-passkey').classList.toggle('hidden',!result.methods.includes('passkey'));showAuthPane('mfa-pane');}else await completeBrowserLogin(result,event.target);}catch(error){authError(error.message);}});
$('#register-pane').addEventListener('submit',async(event)=>{event.preventDefault();authError();try{state.token='';state.csrf='';await api('/auth/register',{method:'POST',body:JSON.stringify(formJSON(event.target))});event.target.reset();showAuthPane('login-pane');authError('Verification email sent.',true);}catch(error){authError(error.message);}});
$('#mfa-pane').addEventListener('submit',async(event)=>{event.preventDefault();authError();if(!state.pendingMFA)return showAuthPane('login-pane');try{const result=await api('/auth/mfa/totp',{method:'POST',body:JSON.stringify({challenge_token:state.pendingMFA.challenge_token,code:formJSON(event.target).code})});await completeBrowserLogin(result,event.target);}catch(error){authError(error.message);}});
$('#reset-request-pane').addEventListener('submit',async(event)=>{event.preventDefault();authError();try{await api('/auth/password-reset/request',{method:'POST',body:JSON.stringify(formJSON(event.target))});event.target.reset();showAuthPane('login-pane');authError('Password reset email sent if the account exists.',true);}catch(error){authError(error.message);}});
$('#reset-complete-pane').addEventListener('submit',async(event)=>{event.preventDefault();authError();try{await api('/auth/password-reset/complete',{method:'POST',body:JSON.stringify(formJSON(event.target))});history.replaceState({},'',location.pathname);event.target.reset();showAuthPane('login-pane');authError('Password updated. Sign in again.',true);}catch(error){authError(error.message);}});
$('#invite-pane').addEventListener('submit',async(event)=>{event.preventDefault();authError();try{const result=await api('/auth/invite/accept',{method:'POST',body:JSON.stringify(formJSON(event.target))});history.replaceState({},'',location.pathname);await completeBrowserLogin(result,event.target);}catch(error){authError(error.message);}});
$('#passkey-login').addEventListener('click',async()=>{authError();if(!state.authConfig.passkeys_enabled)return authError('Passkeys are not configured.');const email=$('#login-pane [name=email]').value;if(!email)return authError('Enter your email first.');try{const begin=await api('/auth/passkey/login/options',{method:'POST',body:JSON.stringify({email})});const credential=await getPasskey(begin.options);const result=await api('/auth/passkey/login',{method:'POST',body:JSON.stringify({challenge_token:begin.challenge_token,credential})});await completeBrowserLogin(result,$('#login-pane'));}catch(error){authError(error.message);}});
$('#mfa-passkey').addEventListener('click',async()=>{authError();if(!state.pendingMFA)return showAuthPane('login-pane');try{const begin=await api('/auth/mfa/passkey/login/options',{method:'POST',body:JSON.stringify({challenge_token:state.pendingMFA.challenge_token})});const credential=await getPasskey(begin.options);const result=await api('/auth/mfa/passkey/login',{method:'POST',body:JSON.stringify({challenge_token:begin.challenge_token,credential})});await completeBrowserLogin(result,$('#mfa-pane'));}catch(error){authError(error.message);}});
$('#bootstrap-pane').addEventListener('submit',async(event)=>{event.preventDefault();authError();state.csrf='';state.token=formJSON(event.target).token.trim();if(!await loadAll()){state.token='';authError('Bootstrap token is invalid');}event.target.reset();});
$('#sign-out').addEventListener('click',async()=>{try{if(!state.token)await api('/auth/logout',{method:'POST',body:'{}'});}catch(error){if(error.status!==401)toast(error.message,true);}state.token='';state.csrf='';state.actor={};state.permissions=new Set();setConnected(false);});
$('#tenant-form').addEventListener('submit',async(event)=>{event.preventDefault();try{await api('/tenants',{method:'POST',body:JSON.stringify(formJSON(event.target))});event.target.reset();await loadAll();toast('Tenant created');}catch(error){toast(error.message,true);}});
$('#project-form').addEventListener('submit',async(event)=>{event.preventDefault();try{await api('/projects',{method:'POST',body:JSON.stringify(formJSON(event.target))});event.target.reset();await loadAll();toast('Project created');}catch(error){toast(error.message,true);}});
$('#key-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const form=event.target;const data=formJSON(form);data.scopes=data.scopes.split(',').map(value=>value.trim()).filter(Boolean);data.tags=data.tags.split(',').map(value=>value.trim()).filter(Boolean);data.allowed_models=[...$('#key-models').selectedOptions].map(option=>option.value);data.monthly_spend_micros=data.monthly_spend.trim()?decimalToScaled(data.monthly_spend,6):0;data.daily_spend_micros=data.daily_spend.trim()?decimalToScaled(data.daily_spend,6):0;data.requests_per_minute=Number(data.requests_per_minute||0);data.tokens_per_minute=Number(data.tokens_per_minute||0);delete data.monthly_spend;delete data.daily_spend;data.expires_at=data.expires_at?new Date(data.expires_at).toISOString():null;const result=await api('/keys',{method:'POST',body:JSON.stringify(data)});state.playgroundKey=result.key;form.reset();showSecret('API key created',result.key);await loadAll();goTo('quickstart');if(result.runtime_sync_status!=='applied')toast('API key created; runtime reload is pending');}catch(error){toast(error.message,true);}});
$('#starter-key-form').addEventListener('submit',async(event)=>{event.preventDefault();const button=$('#starter-key-submit');try{const projectID=$('#starter-project').value;const name=$('#starter-key-name').value.trim();const model=selectedDeveloperModel();if(!projectID||!name||!model.public_id)throw new Error('An active project and model are required');button.disabled=true;const result=await api('/keys',{method:'POST',body:JSON.stringify({tenant_id:state.actor.tenant_id,project_id:projectID,name,scopes:['inference'],tags:['quickstart'],allowed_models:[model.public_id],monthly_spend_micros:0,daily_spend_micros:0,requests_per_minute:0,tokens_per_minute:0,expires_at:null})});state.playgroundKey=result.key;await loadAll();$('#playground-key').value=result.key;showSecret('Starter API key created',result.key);toast(result.runtime_sync_status==='applied'?'Starter key is ready in the Playground':'Starter key created; runtime reload is pending');}catch(error){toast(error.message,true);}finally{button.disabled=false;}});
function syncProviderWireAPI(){const form=$('#provider-form');const wire=form.elements.wire_api;const protocol=form.elements.protocol.value;wire.innerHTML=protocol==='anthropic'?'<option value="messages">Messages</option>':'<option value="chat_completions">Chat Completions</option><option value="responses">Responses</option><option value="embeddings">Embeddings</option>';}
$('#provider-form [name=protocol]').addEventListener('change',syncProviderWireAPI);
$('#provider-form').addEventListener('submit',async(event)=>{event.preventDefault();try{await api('/providers',{method:'POST',body:JSON.stringify(formJSON(event.target))});event.target.reset();syncProviderWireAPI();await loadAll();toast('Provider added');}catch(error){toast(error.message,true);}});
$('#model-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const data=formJSON(event.target);data.input_price_micros_per_million=decimalToScaled(data.input_price,6);data.output_price_micros_per_million=decimalToScaled(data.output_price,6);data.cache_read_price_micros_per_million=decimalToScaled(data.cache_read_price,6);data.cache_write_price_micros_per_million=decimalToScaled(data.cache_write_price,6);delete data.input_price;delete data.output_price;delete data.cache_read_price;delete data.cache_write_price;for(const field of ['capabilities','input_modalities','output_modalities','regions','aliases','allowed_tenant_ids','allowed_key_ids'])data[field]=String(data[field]||'').split(',').map(value=>value.trim()).filter(Boolean);data.context_window=Number(data.context_window||0);data.max_output_tokens=Number(data.max_output_tokens||0);data.price_currency=state.overview.billing_currency||'usd';data.routes=$$('.route-row').map(row=>({provider_id:row.querySelector('.route-provider').value,upstream_model:row.querySelector('.route-upstream').value,priority:Number(row.querySelector('.route-priority').value),weight:Number(row.querySelector('.route-weight').value)}));await api('/models',{method:'POST',body:JSON.stringify(data)});event.target.reset();$('#route-editor').innerHTML='';renderRouteEditor();await loadAll();toast('Model created');}catch(error){toast(error.message,true);}});
$('#topup-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const data=formJSON(event.target);const digits=currencyDigits(state.overview.billing_currency||'usd');const result=await api('/billing/checkout-sessions',{method:'POST',body:JSON.stringify({tenant_id:data.tenant_id,amount_minor:decimalToScaled(data.amount,digits)})});window.location.assign(result.url);}catch(error){toast(error.message,true);}});
$('#billing-profile-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const data=formJSON(event.target);data.country=String(data.country||'').toUpperCase();state.billingProfile=await api('/billing/profile',{method:'PUT',body:JSON.stringify(data)});renderBillingProfile();toast(state.billingProfile.stripe_sync_status==='synced'?'Invoice details saved and synced':'Invoice details saved');}catch(error){try{state.billingProfile=await api('/billing/profile');renderBillingProfile();}catch{}toast(error.message,true);}});
$('#auto-topup-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const currency=state.autoTopUp.currency||state.overview.billing_currency||'usd';const result=await api('/billing/auto-topup',{method:'PUT',body:JSON.stringify({enabled:$('#auto-topup-enabled').checked,threshold_micros:decimalToScaled($('#auto-topup-threshold').value,6),topup_amount_minor:decimalToScaled($('#auto-topup-amount').value,currencyDigits(currency))})});state.autoTopUp=result;renderAutoTopUp();renderQuickstart();toast(result.enabled?'Automatic top-up enabled':'Automatic top-up settings saved');}catch(error){toast(error.message,true);}});
$('#auto-topup-payment-setup').addEventListener('click',async()=>{try{const result=await api('/billing/auto-topup/setup-sessions',{method:'POST',body:'{}'});window.location.assign(result.url);}catch(error){toast(error.message,true);}});
$('#developer-preferences-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const data=formJSON(event.target);await api('/developer/preferences',{method:'PUT',body:JSON.stringify({default_model:data.default_model||'',fallback_model:data.fallback_model||''})});await loadAll();toast('API defaults saved');}catch(error){toast(error.message,true);}});
$('#billing-preferences-form').addEventListener('submit',async(event)=>{event.preventDefault();try{await api('/developer/preferences/billing',{method:'PUT',body:JSON.stringify({low_balance_enabled:$('#low-balance-enabled').checked,low_balance_threshold_micros:decimalToScaled($('#low-balance-threshold').value,6),spend_anomaly_enabled:$('#spend-anomaly-enabled').checked,spend_anomaly_multiplier:Number($('#spend-anomaly-multiplier').value),spend_anomaly_min_micros:decimalToScaled($('#spend-anomaly-minimum').value,6)})});await loadAll();toast('Billing alerts saved');}catch(error){toast(error.message,true);}});
$('#adjustment-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const data=formJSON(event.target);await api('/billing/adjustments',{method:'POST',body:JSON.stringify({tenant_id:data.tenant_id,amount_micros:decimalToScaled(data.amount,6),description:data.description})});event.target.reset();await loadAll();toast('Balance adjusted');}catch(error){toast(error.message,true);}});
$('#user-form').addEventListener('submit',async(event)=>{event.preventDefault();try{await api('/users',{method:'POST',body:JSON.stringify(formJSON(event.target))});event.target.reset();await loadAll();toast('Invitation sent');}catch(error){toast(error.message,true);}});
$('#password-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const data=formJSON(event.target);await api('/auth/password',{method:'POST',body:JSON.stringify({current_password:data.current_password,new_password:data.new_password})});event.target.reset();state.csrf='';state.actor={};state.permissions=new Set();setConnected(false);authError('Password changed. Sign in again.',true);}catch(error){toast(error.message,true);}});
$('#totp-begin-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const data=formJSON(event.target);if(state.mfa.totp_enabled){await api('/auth/mfa/totp/disable',{method:'POST',body:JSON.stringify(data)});event.target.reset();await loadAll();toast('TOTP disabled');return;}const result=await api('/auth/mfa/totp/begin',{method:'POST',body:JSON.stringify({current_password:data.current_password})});$('#totp-qr').src=result.qr_code;$('#totp-secret').textContent=result.secret;$('#totp-confirm-form').classList.remove('hidden');toast('Authenticator enrollment started');}catch(error){toast(error.message,true);}});
$('#totp-confirm-form').addEventListener('submit',async(event)=>{event.preventDefault();try{const result=await api('/auth/mfa/totp/confirm',{method:'POST',body:JSON.stringify(formJSON(event.target))});event.target.reset();$('#totp-confirm-form').classList.add('hidden');showSecret('Recovery codes',result.recovery_codes.join('\n'));await loadAll();}catch(error){toast(error.message,true);}});
$('#passkey-form').addEventListener('submit',async(event)=>{event.preventDefault();try{if(!state.authConfig.passkeys_enabled)throw new Error('Passkeys are not configured');const data=formJSON(event.target);const begin=await api('/auth/mfa/passkey/options',{method:'POST',body:JSON.stringify({current_password:data.current_password})});const credential=await createPasskey(begin.options);await api('/auth/mfa/passkey',{method:'POST',body:JSON.stringify({challenge_token:begin.challenge_token,name:data.name,credential})});event.target.reset();await loadAll();toast('Passkey added');}catch(error){toast(error.message,true);}});
$('#revoke-other-sessions').addEventListener('click',async()=>{if(!confirm('Sign out every other device?'))return;try{await api('/auth/sessions/revoke-others',{method:'POST',body:'{}'});await loadAll();toast('Other devices signed out');}catch(error){toast(error.message,true);}});
$('#close-dialog').addEventListener('click',()=>$('#secret-dialog').close());$('#copy-secret').addEventListener('click',async()=>{await navigator.clipboard.writeText($('#created-secret').textContent);toast('Credential copied');});
$('#close-model-dialog').addEventListener('click',()=>$('#model-dialog').close());$('#model-dialog-close').addEventListener('click',()=>$('#model-dialog').close());$('#model-dialog-playground').addEventListener('click',()=>{if(state.detailModel)useModelInPlayground(state.detailModel.public_id);});$('#copy-model-id').addEventListener('click',async()=>{await navigator.clipboard.writeText($('#model-dialog-id').textContent);toast('Model ID copied');});
$('#close-usage-dialog').addEventListener('click',()=>$('#usage-dialog').close());$('#usage-dialog-close').addEventListener('click',()=>$('#usage-dialog').close());$('#copy-request-id').addEventListener('click',async()=>{await navigator.clipboard.writeText(state.detailUsage?.request_id||'');toast('Request ID copied');});$('#copy-request-diagnostic').addEventListener('click',async()=>{await navigator.clipboard.writeText($('#usage-diagnostic').textContent);toast('Diagnostic copied');});
$('#release-reservation-form').addEventListener('submit',async(event)=>{event.preventDefault();const item=state.detailUsage;if(!item)return;const reason=event.target.elements.reason.value.trim();if(!reason)return;const params=new URLSearchParams({tenant_id:item.tenant_id});try{await api(`/billing/reservations/${encodeURIComponent(item.request_id)}/release?${params}`,{method:'POST',body:JSON.stringify({reason})});$('#usage-dialog').close();await loadAll();toast('Reservation hold released');}catch(error){toast(error.message,true);}});
$('#model-estimate-form').addEventListener('submit',event=>event.preventDefault());
['estimate-input','estimate-output','estimate-cache-read','estimate-cache-write'].forEach(id=>$('#'+id).addEventListener('input',renderModelEstimate));
$('#quickstart-model').addEventListener('change',()=>{syncQuickstartProtocols();renderQuickstartCode();renderDeveloperAccess();});
$('#quickstart-protocol').addEventListener('change',()=>{syncProviderSelect('#quickstart-provider',selectedDeveloperModel(),$('#quickstart-protocol').value);renderQuickstartCode();});
$('#quickstart-provider').addEventListener('change',renderQuickstartCode);
$('#quickstart-language').addEventListener('change',renderQuickstartCode);
$('#playground-model').addEventListener('change',syncPlaygroundProtocols);
$('#playground-protocol').addEventListener('change',syncPlaygroundProtocols);
$('#playground-form').addEventListener('submit',runPlayground);
$('#playground-stop').addEventListener('click',()=>state.playgroundController?.abort());
$('#playground-diagnostic-action').addEventListener('click',event=>goTo(event.currentTarget.dataset.target));
$('#catalog-search').addEventListener('input',renderCatalog);$('#catalog-protocol').addEventListener('change',renderCatalog);$('#catalog-input').addEventListener('change',renderCatalog);$('#catalog-owner').addEventListener('change',renderCatalog);$('#catalog-sort').addEventListener('change',renderCatalog);
$('#copy-quickstart').addEventListener('click',async()=>{try{await navigator.clipboard.writeText($('#quickstart-code-block code').textContent);toast('Example copied');}catch(error){toast('Copy failed; select the example manually',true);}});
$('#copy-endpoint-env').addEventListener('click',async()=>{try{await navigator.clipboard.writeText($('#endpoint-env').textContent);toast('Environment copied');}catch(error){toast('Copy failed; select the environment manually',true);}});
function usageFilterParams(){const params=new URLSearchParams(new FormData($('#usage-filter-form')));for(const [key,value] of [...params.entries()])if(!String(value).trim())params.delete(key);if(state.actor.tenant_id)params.delete('provider');return params;}
async function loadUsageFilters({cursor='',history=[],refreshAggregates=true}={}){
  const params=usageFilterParams(),pageParams=new URLSearchParams(params);pageParams.set('limit','50');if(cursor)pageParams.set('cursor',cursor);
  try{
    const pagePromise=api(`/usage?${pageParams}`);let page;
    if(refreshAggregates){[page,state.usageDaily,state.usageAnalytics]=await Promise.all([pagePromise,api(`/usage/daily${params.size?`?${params}`:''}`),api(`/usage/analytics${params.size?`?${params}`:''}`)]);}else page=await pagePromise;
    state.usage=Array.isArray(page)?page:(page.data||[]);state.usagePaging={cursor,nextCursor:page.next_cursor||'',history};renderUsage();
  }catch(error){toast(error.message,true);}
}
$('#usage-filter-form').addEventListener('submit',async(event)=>{event.preventDefault();await loadUsageFilters();});
$('#usage-filter-reset').addEventListener('click',async()=>{const form=$('#usage-filter-form');form.reset();const query=new URLSearchParams(defaultUsageQuery());$('#usage-from').value=query.get('from');$('#usage-to').value=query.get('to');await loadUsageFilters();});
$('#usage-page-next').addEventListener('click',async()=>{if(!state.usagePaging.nextCursor)return;await loadUsageFilters({cursor:state.usagePaging.nextCursor,history:[...state.usagePaging.history,state.usagePaging.cursor],refreshAggregates:false});});
$('#usage-page-prev').addEventListener('click',async()=>{if(!state.usagePaging.history.length)return;const history=state.usagePaging.history.slice(0,-1);await loadUsageFilters({cursor:state.usagePaging.history.at(-1)||'',history,refreshAggregates:false});});
async function pollTopUp(orderID){for(let attempt=0;attempt<20;attempt++){const order=await api(`/billing/orders/${encodeURIComponent(orderID)}`);if(order.status==='paid'){await loadAll();toast('Balance credited');return;}if(['failed','expired'].includes(order.status)){toast(`Top-up ${order.status}`,true);return;}await new Promise(resolve=>setTimeout(resolve,1500));}toast('Payment is still processing');}
async function pollAutoTopUpSetup(){for(let attempt=0;attempt<20;attempt++){const settings=await api('/billing/auto-topup');if(settings.payment_method_configured){state.autoTopUp=settings;renderBilling();renderQuickstart();toast('Payment method saved; review the threshold and enable automatic top-up');return;}await new Promise(resolve=>setTimeout(resolve,1500));}toast('Payment method setup is still processing',true);}
async function start(){try{state.authConfig=await api('/auth/config');$('#register-tab').classList.toggle('hidden',!state.authConfig.registration_enabled);if(!state.authConfig.registration_enabled&&$('#register-tab').classList.contains('active'))showAuthPane('login-pane');const params=new URLSearchParams(location.search);const action=params.get('action');const token=params.get('token');if(params.get('auth')==='register'&&state.authConfig.registration_enabled)showAuthPane('register-pane');if(action==='reset-password'&&token){$('#reset-token').value=token;showAuthPane('reset-complete-pane');setConnected(false);return;}if(action==='accept-invite'&&token){$('#invite-token').value=token;showAuthPane('invite-pane');setConnected(false);return;}state.csrf=cookie('aigw_csrf');if(action==='verify-email'&&token){const result=await api('/auth/email/verify',{method:'POST',body:JSON.stringify({token})});history.replaceState({},'',location.pathname);await completeBrowserLogin(result);return;}const session=await api('/auth/session');if(session.authenticated){await loadAll(session);const orderID=params.get('order_id');if(params.get('topup')==='success'&&orderID){history.replaceState({},'',location.pathname);pollTopUp(orderID).catch(error=>toast(error.message,true));}else if(params.get('topup')==='cancel'){history.replaceState({},'',location.pathname);toast('Top-up cancelled');}else if(params.get('autotopup')==='setup'){history.replaceState({},'',location.pathname);pollAutoTopUpSetup().catch(error=>toast(error.message,true));}else if(params.get('autotopup')==='cancel'){history.replaceState({},'',location.pathname);toast('Payment method setup cancelled');}}else setConnected(false);}catch(error){setConnected(false);authError(error.message);}}
start();