Cost dashboard: TTS providers endpoint, header link, polish

Adds /api/costs/tts endpoint backed by get_tts_providers() in cost_db,
surfaces a Costs link in the control panel header, and small polish
to the costs page HTML/JS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-09 23:39:40 -06:00
co-authored by Claude Opus 4.6
parent 1b988a0303
commit c087c0362a
6 changed files with 68 additions and 3 deletions
+12 -1
View File
@@ -59,7 +59,16 @@
<canvas id="session-chart"></canvas>
</div>
</section>
<section class="tables-section">
<section class="chart-row">
<div class="table-container">
<h3>TTS Providers</h3>
<table id="tts-table">
<thead>
<tr><th>Provider</th><th>Calls</th><th>Characters</th><th>Cost</th></tr>
</thead>
<tbody></tbody>
</table>
</div>
<div class="table-container">
<h3>Most Expensive Calls</h3>
<table id="expensive-table">
@@ -69,6 +78,8 @@
<tbody></tbody>
</table>
</div>
</section>
<section class="tables-section">
<div class="table-container">
<h3>Sessions</h3>
<table id="sessions-table">
+14 -1
View File
@@ -108,11 +108,24 @@ header button {
transition: all 0.2s;
}
header button:hover {
header button:hover, .header-link-btn:hover {
background: #3a2e1f;
border-color: rgba(232, 121, 29, 0.3);
}
.header-link-btn {
background: var(--bg-light);
color: var(--text);
border: 1px solid rgba(232, 121, 29, 0.15);
padding: 8px 16px;
border-radius: var(--radius-sm);
cursor: pointer;
transition: all 0.2s;
text-decoration: none;
font-size: inherit;
font-family: inherit;
}
.theme-bar {
display: flex;
align-items: center;
+1
View File
@@ -17,6 +17,7 @@
<button id="export-session-btn">Export</button>
<button id="preflight-btn" class="preflight-btn">Preflight</button>
<button id="settings-btn">Settings</button>
<a href="/costs" class="header-link-btn" title="Cost Dashboard">Costs</a>
</div>
<div class="theme-bar">
<label for="show-theme-input" class="theme-label">Theme:</label>
+15 -1
View File
@@ -50,19 +50,21 @@ function destroyChart(key) {
async function loadDashboard() {
const p = currentPeriod;
try {
const [summary, timeline, models, categories, sessions, expensive] = await Promise.all([
const [summary, timeline, models, categories, sessions, expensive, tts] = await Promise.all([
fetch(`/api/costs/summary?period=${p}`).then(r => r.json()),
fetch(`/api/costs/timeline?period=${p}&group_by=session`).then(r => r.json()),
fetch(`/api/costs/models?period=${p}`).then(r => r.json()),
fetch(`/api/costs/categories?period=${p}`).then(r => r.json()),
fetch(`/api/costs/sessions?period=${p}`).then(r => r.json()),
fetch(`/api/costs/expensive?period=${p}&limit=10`).then(r => r.json()),
fetch(`/api/costs/tts?period=${p}`).then(r => r.json()),
]);
renderSummary(summary);
renderTimeline(timeline);
renderModels(models);
renderCategories(categories);
renderSessionBars(timeline);
renderTtsTable(tts);
renderExpensiveTable(expensive);
renderSessionsTable(sessions);
} catch (e) {
@@ -201,6 +203,18 @@ function renderSessionBars(data) {
});
}
function renderTtsTable(data) {
const tbody = document.querySelector('#tts-table tbody');
tbody.innerHTML = data.map(d => `
<tr>
<td>${d.provider}</td>
<td>${d.calls.toLocaleString()}</td>
<td>${d.chars.toLocaleString()}</td>
<td>${formatCost(d.cost)}</td>
</tr>
`).join('');
}
function renderExpensiveTable(data) {
const tbody = document.querySelector('#expensive-table tbody');
tbody.innerHTML = data.map(d => `