Feat: Add Token Stats and Cost Savings dashboard card and backend tracking

This commit is contained in:
Hitonabi
2026-06-26 13:48:32 +02:00
parent 311f4d7b68
commit 2e4cddc840
9 changed files with 546 additions and 391 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -7,8 +7,8 @@
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>Mission Control 2.0</title>
<script type="module" crossorigin src="/assets/index-BPaKNJdw.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D8yfJQ7Z.css">
<script type="module" crossorigin src="/assets/index-CNk5p9Ix.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Djoh-Jwz.css">
</head>
<body>
<div id="root"></div>
+62 -3
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react"
import { Bot, Cpu, ExternalLink, Brain, Layers, Plus, ShieldAlert, X, Power, Shield, Download, RefreshCw, Check } from "lucide-react"
import { Bot, Cpu, ExternalLink, Brain, Layers, Plus, ShieldAlert, X, Power, Shield, Download, RefreshCw, Check, Coins } from "lucide-react"
import { api, type SystemStatus, type AgentStatus, type ModelInfo, type Memory, type UpdatesResp, type Job } from "@/lib/api"
import { cn, resolveExternalUrl } from "@/lib/utils"
import { CustomDialog } from "@/components/CustomDialog"
@@ -43,6 +43,13 @@ export function DashboardView() {
const [memories, setMemories] = useState<Memory[]>([])
const [updates, setUpdates] = useState<UpdatesResp | null>(null)
const [jobs, setJobs] = useState<Job[]>([])
const [tokenStats, setTokenStats] = useState<{
prompt_tokens: number
completion_tokens: number
total_tokens: number
saved_usd: number
saved_eur: number
} | null>(null)
// Sudo & Action states
const [msg, setMsg] = useState("")
@@ -123,6 +130,7 @@ export function DashboardView() {
api<Memory[]>("/api/memory?category=").then((d) => setMemories(d.slice(0, 3))).catch(() => {})
api<UpdatesResp>("/api/maintenance/updates").then(setUpdates).catch(() => {})
api<{ jobs: Job[] }>("/api/jobs").then((d) => setJobs(d.jobs || [])).catch(() => {})
api<any>("/api/system/token-stats").then(setTokenStats).catch(() => {})
}
useEffect(() => {
@@ -495,8 +503,8 @@ export function DashboardView() {
</div>
</div>
{/* Bottom Grid: Agent, Roles & Memory (3 Columns) */}
<div className="grid gap-6 md:grid-cols-3">
{/* Bottom Grid: Agent, Roles, Memory & Token Stats (4 Columns on Desktop) */}
<div className="grid gap-6 md:grid-cols-2 xl:grid-cols-4">
{/* Card 3: Hermes Agent Status */}
<div className="rounded-2xl border border-border/60 bg-card/45 backdrop-blur-md p-5 shadow-lg shadow-black/15 flex flex-col justify-between">
<div>
@@ -700,6 +708,57 @@ export function DashboardView() {
Steht allen Clients per MCP zur Verfügung.
</div>
</div>
{/* Card 6: Effizienz & Ersparnis (Token-Stats & Ersparnis) */}
<div className="rounded-2xl border border-border/60 bg-card/45 backdrop-blur-md p-5 shadow-lg shadow-black/15 flex flex-col justify-between">
<div>
<div className="flex items-center gap-2 mb-4">
<Coins className="h-4.5 w-4.5 text-primary animate-pulse" />
<h2 className="text-sm font-semibold tracking-wide uppercase text-foreground">Effizienz &amp; Ersparnis</h2>
</div>
{tokenStats ? (
<div className="space-y-4">
<div className="grid grid-cols-2 gap-2.5">
<div className="p-3 bg-background/20 rounded-xl border border-border/40 text-left">
<div className="text-[8px] uppercase tracking-wider font-semibold text-muted-foreground/60">Geld gespart</div>
<div className="text-base font-bold text-emerald-400 mt-0.5 tracking-tight font-space">
{tokenStats.saved_eur.toLocaleString("de-DE", { minimumFractionDigits: 2 })}
</div>
<div className="text-[8px] text-muted-foreground/80 mt-0.5 font-mono">
({tokenStats.saved_usd.toLocaleString("en-US", { minimumFractionDigits: 2 })} $)
</div>
</div>
<div className="p-3 bg-background/20 rounded-xl border border-border/40 text-left">
<div className="text-[8px] uppercase tracking-wider font-semibold text-muted-foreground/60">Gesamt-Tokens</div>
<div className="text-base font-bold text-primary mt-0.5 tracking-tight font-space">
{tokenStats.total_tokens.toLocaleString("de-DE")}
</div>
<div className="text-[8px] text-muted-foreground/80 mt-0.5 font-mono">
(Lokale Inferenz)
</div>
</div>
</div>
<div className="space-y-2 border-t border-border/20 pt-3 text-[10px] text-muted-foreground">
<div className="flex justify-between items-center font-mono">
<span>Input (Prompts):</span>
<span className="font-semibold text-foreground">{tokenStats.prompt_tokens.toLocaleString("de-DE")} tkn</span>
</div>
<div className="flex justify-between items-center font-mono">
<span>Output (Antworten):</span>
<span className="font-semibold text-foreground">{tokenStats.completion_tokens.toLocaleString("de-DE")} tkn</span>
</div>
</div>
</div>
) : (
<div className="h-28 flex items-center justify-center text-xs text-muted-foreground">Lade Statistiken</div>
)}
</div>
<div className="mt-4 text-[9px] text-muted-foreground/70 border-t border-border/30 pt-2.5 leading-normal">
Berechnet im Vergleich zu Cloud-APIs (Ø 3,00 $ / 15,00 $ pro 1M tkn).
</div>
</div>
</div>
{agent && showBrainSelect && (