Feat: Add Token Stats and Cost Savings dashboard card and backend tracking
This commit is contained in:
@@ -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 & 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 && (
|
||||
|
||||
Reference in New Issue
Block a user