From 47c743f17c655c44f14b443eca9e9ff34ce079d2 Mon Sep 17 00:00:00 2001 From: Liu Date: Tue, 7 Apr 2026 16:11:32 +0800 Subject: [PATCH] fix: avoid Alpine SVG template rendering errors in cost charts --- crates/openfang-api/static/index_body.html | 51 +--------------- crates/openfang-api/static/js/pages/usage.js | 63 ++++++++++++++++++++ 2 files changed, 65 insertions(+), 49 deletions(-) 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)

- + TOTAL @@ -4085,41 +4072,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() {