From 48c1892fea6274c0c3a73c4b63da59a9a328552d Mon Sep 17 00:00:00 2001 From: Claude Dev Date: Thu, 26 Mar 2026 20:44:20 +0100 Subject: [PATCH] feat: Token-Nutzung nach Quelle (Monitor/Globe) aufgeschluesselt - Backend liefert usage_by_source im current-Endpoint - Monatliche Tabelle zeigt Quelle-Badge (Monitor/Globe) - Source-Split unter den Kosten-KPIs sichtbar Co-Authored-By: Claude Opus 4.6 (1M context) --- src/routers/token_usage.py | 33 +++++++++++++++++++++++++++------ src/static/dashboard.html | 10 +++++++++- src/static/js/app.js | 11 +++++++++++ 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/routers/token_usage.py b/src/routers/token_usage.py index 6fcecfd..c586635 100644 --- a/src/routers/token_usage.py +++ b/src/routers/token_usage.py @@ -76,6 +76,7 @@ async def get_org_usage(org_id: int, admin=Depends(get_current_admin)): return [{ "year_month": row["year_month"], + "source": row["source"] if "source" in row.keys() else "monitor", "input_tokens": row["input_tokens"], "output_tokens": row["output_tokens"], "cache_creation_tokens": row["cache_creation_tokens"], @@ -98,7 +99,26 @@ async def get_org_current_usage(org_id: int, admin=Depends(get_current_admin)): cursor = await db.execute( "SELECT * FROM token_usage_monthly WHERE organization_id = ? AND year_month = ?", (org_id, year_month)) - usage = await cursor.fetchone() + usage_rows = await cursor.fetchall() + # Summe ueber alle Sources + usage = { + "input_tokens": sum(r["input_tokens"] for r in usage_rows), + "output_tokens": sum(r["output_tokens"] for r in usage_rows), + "total_cost_usd": sum(r["total_cost_usd"] for r in usage_rows), + "api_calls": sum(r["api_calls"] for r in usage_rows), + "refresh_count": sum(r["refresh_count"] for r in usage_rows), + } + # Per-Source Aufschluesselung + usage_by_source = {} + for r in usage_rows: + src = r["source"] if "source" in r.keys() else "monitor" + usage_by_source[src] = { + "input_tokens": r["input_tokens"], + "output_tokens": r["output_tokens"], + "total_cost_usd": round(r["total_cost_usd"], 2), + "api_calls": r["api_calls"], + "refresh_count": r["refresh_count"], + } cursor = await db.execute( "SELECT credits_total, credits_used, cost_per_credit, token_budget_usd, budget_warning_percent FROM licenses WHERE organization_id = ? AND status = 'active' ORDER BY id DESC LIMIT 1", @@ -111,12 +131,13 @@ async def get_org_current_usage(org_id: int, admin=Depends(get_current_admin)): return { "year_month": year_month, "usage": { - "input_tokens": usage["input_tokens"] if usage else 0, - "output_tokens": usage["output_tokens"] if usage else 0, - "total_cost_usd": round(usage["total_cost_usd"], 2) if usage else 0, - "api_calls": usage["api_calls"] if usage else 0, - "refresh_count": usage["refresh_count"] if usage else 0, + "input_tokens": usage["input_tokens"], + "output_tokens": usage["output_tokens"], + "total_cost_usd": round(usage["total_cost_usd"], 2), + "api_calls": usage["api_calls"], + "refresh_count": usage["refresh_count"], }, + "usage_by_source": usage_by_source, "budget": { "credits_total": credits_total, "credits_used": round(credits_used, 1) if credits_used else 0, diff --git a/src/static/dashboard.html b/src/static/dashboard.html index a629e83..1bb3692 100644 --- a/src/static/dashboard.html +++ b/src/static/dashboard.html @@ -7,7 +7,13 @@ - + + +
@@ -188,6 +194,7 @@
-
+
Budget-Auslastung @@ -202,6 +209,7 @@ Monat + Quelle Refreshes API-Calls Input-Tokens diff --git a/src/static/js/app.js b/src/static/js/app.js index 47fcf0e..dd79e03 100644 --- a/src/static/js/app.js +++ b/src/static/js/app.js @@ -606,6 +606,14 @@ async function loadOrgTokenUsage(orgId) { el('tokenBudgetUsd', budget.token_budget_usd != null ? '$' + Number(budget.token_budget_usd).toFixed(2) : '-'); el('tokenCostUsd', usage.total_cost_usd != null ? '$' + Number(usage.total_cost_usd).toFixed(2) : '-'); + // Source-Split anzeigen + var bySource = current.usage_by_source || {}; + var splitHtml = ''; + if (bySource.monitor) splitHtml += 'Monitor $' + Number(bySource.monitor.total_cost_usd).toFixed(2) + ' (' + bySource.monitor.api_calls + ' Calls) '; + if (bySource.globe) splitHtml += 'Globe $' + Number(bySource.globe.total_cost_usd).toFixed(2) + ' (' + bySource.globe.api_calls + ' Calls)'; + var splitEl = document.getElementById('tokenSourceSplit'); + if (splitEl) splitEl.innerHTML = splitHtml; + const bar = document.getElementById('tokenBudgetBar'); const percentEl = document.getElementById('tokenBudgetPercent'); const percent = budget.credits_percent_used || 0; @@ -622,8 +630,11 @@ async function loadOrgTokenUsage(orgId) { const tbody = document.getElementById('tokenMonthlyTable'); if (tbody) { tbody.innerHTML = monthly.map(function(m) { + var srcLabel = (m.source || 'monitor') === 'globe' ? 'Globe' : 'Monitor'; + var srcClass = (m.source || 'monitor') === 'globe' ? 'badge-globe' : 'badge-monitor'; return '' + '' + esc(m.year_month) + '' + + '' + srcLabel + '' + '' + m.refresh_count + '' + '' + m.api_calls.toLocaleString('de-DE') + '' + '' + m.input_tokens.toLocaleString('de-DE') + '' +