diff --git a/crates/openfang-api/static/index_body.html b/crates/openfang-api/static/index_body.html
index b8f7f3d8d9..825b95b8fa 100644
--- a/crates/openfang-api/static/index_body.html
+++ b/crates/openfang-api/static/index_body.html
@@ -4045,20 +4045,7 @@
Top Spenders (Today)
diff --git a/crates/openfang-api/static/js/pages/usage.js b/crates/openfang-api/static/js/pages/usage.js
index cf1c77303d..c2ea5b6ca5 100644
--- a/crates/openfang-api/static/js/pages/usage.js
+++ b/crates/openfang-api/static/js/pages/usage.js
@@ -191,6 +191,33 @@ function analyticsPage() {
return segments;
},
+ donutSegmentsSvg() {
+ var segments = this.donutSegments();
+ if (!segments.length) return '';
+
+ function escapeXml(value) {
+ return String(value)
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+ }
+
+ var out = [];
+ for (var i = 0; i < segments.length; i++) {
+ var seg = segments[i];
+ var title = seg.provider + ': ' + seg.percent + '% (' + this.formatCost(seg.cost) + ')';
+ out.push(
+ '' +
+ '' + escapeXml(title) + '' +
+ ''
+ );
+ }
+
+ return out.join('');
+ },
+
// ── Bar chart (last 7 days) ──
barChartData() {
@@ -218,6 +245,42 @@ function analyticsPage() {
return result;
},
+ barChartSvg() {
+ var bars = this.barChartData();
+ if (!bars.length) return '';
+
+ function escapeXml(value) {
+ return String(value)
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+ }
+
+ var out = [];
+ for (var i = 0; i < bars.length; i++) {
+ var bar = bars[i];
+ var x = i * 50 + 18;
+ var labelX = i * 50 + 30;
+ var y = 150 - bar.barHeight;
+ var costLabelY = y - 4;
+ var title = bar.date + ': ' + this.formatCost(bar.cost) + ' (' + bar.calls + ' calls)';
+
+ out.push(
+ '' +
+ '' +
+ '' + escapeXml(title) + '' +
+ '' +
+ '' + escapeXml(bar.dayName) + '' +
+ '' + escapeXml(this.formatCost(bar.cost)) + '' +
+ ''
+ );
+ }
+
+ return out.join('');
+ },
+
// ── Cost by model table (sorted by cost descending) ──
costByModelSorted() {