/* =========================================================================
dashboard.js — the user's DreamRouter account view
• auth guard: Google session cookie only (no API-key sign-in exists)
• loads YOUR usage (GET /api/me/billing) + model list (GET /v1/models)
• renders usage stat cards, model grid, and a streaming playground
• full state coverage: loading skeletons → populated → empty → error
========================================================================= */
(async function () {
'use strict';
// where the router lives (same origin when deployed to HF Space)
var BASE = window.location.origin;
var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
/* ---- auth guard -------------------------------------------------------- */
// The ONLY way in is a Google session cookie. Probe it via /auth/me.
// (No sessionStorage key path anymore — API-key login was removed.)
var greet = document.getElementById('greet');
var ssoUser = null;
try {
var res = await fetch('/auth/me');
if (res.ok) {
var me = await res.json();
ssoUser = me.username || me.email || null; // google display name
} else {
// not signed in → back to the Google-only login screen
window.location.replace('login.html');
return;
}
} catch (e) {
window.location.replace('login.html');
return;
}
// personalise the greeting
if (ssoUser) {
greet.textContent = 'welcome, ' + ssoUser;
}
/* ---- element refs ------------------------------------------------------ */
var statsRow = document.getElementById('stats-row');
var modelGrid = document.getElementById('model-grid');
var pgForm = document.getElementById('pg-form');
var pgModel = document.getElementById('pg-model');
var pgTokens = document.getElementById('pg-tokens');
var pgPrompt = document.getElementById('pg-prompt');
var pgOutput = document.getElementById('pg-output');
var pgSend = document.getElementById('pg-send');
var pgClear = document.getElementById('pg-clear');
var pgStatus = document.getElementById('pg-status');
var logout = document.getElementById('logout');
var endpointMini = document.getElementById('endpoint-mini');
// show the real endpoint in the header pill
endpointMini.textContent = BASE + '/v1';
/* ---- logout ------------------------------------------------------------ */
logout.addEventListener('click', function () {
// clear the Google session cookie server-side, then return to login
fetch('/auth/logout', { method: 'POST' }).catch(function () {}).finally(function () {
window.location.replace('login.html'); // back to sign-in
});
});
/* ---- small svg icon set (inline, no external dep) --------------------- */
var ICON = {
requests: '',
tokens: '',
alltime: '',
models: '',
down: ''
};
function svgIcon(name, label) {
return '';
}
/* ---- stats: render skeletons first, then real data -------------------- */
function renderSkeletons() {
statsRow.innerHTML = (
statCardSkeleton() + statCardSkeleton() + statCardSkeleton() + statCardSkeleton()
);
}
function statCardSkeleton() {
return '
' +
'' +
'' +
'' +
'
';
}
// render the 4 USER-facing stat cards (no operator/routing-health metrics)
function renderStats(usage, modelCount) {
var today = (usage && usage.today) || {};
var total = (usage && usage.total) || {};
var reqToday = today.requests || 0;
var tokToday = (today.tok_in || 0) + (today.tok_out || 0);
var reqAll = total.requests || 0;
statsRow.innerHTML =
statCard('requests', reqToday.toLocaleString(), 'requests today', '') +
statCard('tokens', tokToday.toLocaleString(), 'tokens today', '') +
statCard('alltime', reqAll.toLocaleString(), 'all-time requests', '') +
statCard('models', modelCount.toLocaleString(), 'models available', '');
}
function statCard(icon, num, label, cls) {
return '
' +
svgIcon(icon) +
'
' + num + '
' +
'
' + label + '
' +
'
';
}
function renderStatsError() {
statsRow.innerHTML =
'
';
}
/* ---- models: skeleton → grid ------------------------------------------ */
function renderModelSkeletons() {
var html = '';
for (var i = 0; i < 12; i++) {
html += '';
}
modelGrid.innerHTML = html;
}
function renderModels(list) {
if (!list || !list.length) {
// empty state: headline + explanation (state-coverage craft)
modelGrid.innerHTML = '
' +
'
No models loaded
' +
'
The router is up but returned no models. Try again in a moment.
';
return;
}
var html = '';
for (var i = 0; i < list.length; i++) {
var m = list[i];
var isAnthropic = m.id.indexOf('anthropic/') === 0;
var isFree = m.id.indexOf('free:') === 0; // free-tier models (no credit cost)
html += '