@acarmisc/backstage-plugin-litellm 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -105,7 +105,6 @@ var init_api = __esm({
105
105
  // src/components/DashboardHeader.tsx
106
106
  import React from "react";
107
107
  import { Box, Typography, LinearProgress, Paper, Chip } from "@mui/material";
108
- import { Warning } from "@mui/icons-material";
109
108
  var DashboardHeader;
110
109
  var init_DashboardHeader = __esm({
111
110
  "src/components/DashboardHeader.tsx"() {
@@ -115,11 +114,6 @@ var init_DashboardHeader = __esm({
115
114
  return /* @__PURE__ */ React.createElement(Paper, { sx: { p: 2, mb: 2 } }, /* @__PURE__ */ React.createElement(LinearProgress, null));
116
115
  }
117
116
  const displayName = userInfo.user_email ?? userInfo.email ?? userInfo.user_id;
118
- const budget = userInfo.max_budget ?? 0;
119
- const spend = userInfo.spend ?? userInfo.current_spend ?? 0;
120
- const budgetPct = budget > 0 ? Math.min(spend / budget * 100, 100) : 0;
121
- const isOver = budget > 0 && spend >= budget;
122
- const isNear = budget > 0 && spend >= budget * 0.8 && !isOver;
123
117
  return /* @__PURE__ */ React.createElement(Paper, { sx: { p: 2, mb: 2 } }, /* @__PURE__ */ React.createElement(Box, { display: "flex", alignItems: "flex-start", gap: 2, flexWrap: "wrap" }, /* @__PURE__ */ React.createElement(Box, { flexGrow: 1 }, /* @__PURE__ */ React.createElement(Typography, { variant: "h6" }, displayName), /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "text.secondary" }, userInfo.user_id), teams.length > 0 && /* @__PURE__ */ React.createElement(Box, { display: "flex", gap: 0.75, flexWrap: "wrap", mt: 1 }, teams.map((team) => /* @__PURE__ */ React.createElement(
124
118
  Chip,
125
119
  {
@@ -129,15 +123,7 @@ var init_DashboardHeader = __esm({
129
123
  variant: "outlined",
130
124
  color: "primary"
131
125
  }
132
- )))), budget > 0 && /* @__PURE__ */ React.createElement(Box, { minWidth: 220 }, /* @__PURE__ */ React.createElement(Box, { display: "flex", justifyContent: "space-between", mb: 0.5 }, /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, "$", spend.toFixed(2), " / $", budget.toFixed(2)), isOver && /* @__PURE__ */ React.createElement(Chip, { icon: /* @__PURE__ */ React.createElement(Warning, null), label: "Over Budget", size: "small", color: "error" }), isNear && /* @__PURE__ */ React.createElement(Chip, { label: "Near Limit", size: "small", color: "warning" })), /* @__PURE__ */ React.createElement(
133
- LinearProgress,
134
- {
135
- variant: "determinate",
136
- value: budgetPct,
137
- color: isOver ? "error" : isNear ? "warning" : "primary",
138
- sx: { height: 8, borderRadius: 1 }
139
- }
140
- ))));
126
+ ))))));
141
127
  };
142
128
  }
143
129
  });
@@ -458,7 +444,6 @@ import {
458
444
  Select,
459
445
  MenuItem as MenuItem2,
460
446
  Grid,
461
- CircularProgress as CircularProgress2,
462
447
  Tabs,
463
448
  Tab,
464
449
  Table as Table2,
@@ -468,42 +453,71 @@ import {
468
453
  TableHead as TableHead2,
469
454
  TableRow as TableRow2,
470
455
  Chip as Chip3,
471
- LinearProgress as LinearProgress2
456
+ LinearProgress as LinearProgress2,
457
+ Skeleton
472
458
  } from "@mui/material";
473
459
  import {
474
460
  AreaChart,
475
461
  Area,
476
462
  BarChart,
477
463
  Bar,
464
+ LineChart,
465
+ Line,
478
466
  XAxis,
479
467
  YAxis,
480
468
  CartesianGrid,
481
469
  Tooltip,
482
470
  ResponsiveContainer,
483
- Legend
471
+ Legend,
472
+ ReferenceLine
484
473
  } from "recharts";
485
- var fmtUsd, fmtInt, fmtPct, KpiCard, UsageStats;
474
+ function modelColor(model) {
475
+ let h = 5381;
476
+ for (let i = 0; i < model.length; i++) h = (h << 5) + h + model.charCodeAt(i) >>> 0;
477
+ return MODEL_COLORS[h % MODEL_COLORS.length];
478
+ }
479
+ var TOP_N_MODELS, PERIOD_LS_KEY, MODEL_COLORS, fmtUsd, fmtInt, fmtPct, KpiCard, ChartSkeleton, EmptyChart, UsageStats;
486
480
  var init_UsageStats = __esm({
487
481
  "src/components/UsageStats.tsx"() {
488
482
  "use strict";
483
+ TOP_N_MODELS = 6;
484
+ PERIOD_LS_KEY = "litellm_usage_period";
485
+ MODEL_COLORS = [
486
+ "#8884d8",
487
+ "#82ca9d",
488
+ "#ffc658",
489
+ "#ff7300",
490
+ "#0088fe",
491
+ "#00C49F",
492
+ "#FFBB28",
493
+ "#FF8042",
494
+ "#a4de6c",
495
+ "#d0ed57"
496
+ ];
489
497
  fmtUsd = (n) => `$${(n ?? 0).toFixed(n < 1 ? 4 : 2)}`;
490
498
  fmtInt = (n) => (n ?? 0).toLocaleString();
491
499
  fmtPct = (n) => `${(n * 100).toFixed(1)}%`;
492
500
  KpiCard = ({ label, value, hint }) => /* @__PURE__ */ React3.createElement(Paper3, { variant: "outlined", sx: { p: 2, height: "100%" } }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption", color: "text.secondary" }, label), /* @__PURE__ */ React3.createElement(Typography3, { variant: "h5", sx: { mt: 0.5 } }, value), hint ? /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption", color: "text.secondary" }, hint) : null);
501
+ ChartSkeleton = ({ height = 260 }) => /* @__PURE__ */ React3.createElement(Skeleton, { variant: "rectangular", height, sx: { borderRadius: 1 } });
502
+ EmptyChart = ({
503
+ height = 260,
504
+ message = "No data for this period"
505
+ }) => /* @__PURE__ */ React3.createElement(Box3, { height, display: "flex", alignItems: "center", justifyContent: "center" }, /* @__PURE__ */ React3.createElement(Typography3, { color: "text.secondary", variant: "body2" }, message));
493
506
  UsageStats = ({
494
507
  usage,
495
508
  models,
496
509
  dateRange,
497
510
  onDateRangeChange,
498
- loading
511
+ loading,
512
+ userInfo
499
513
  }) => {
500
514
  const [selectedModel, setSelectedModel] = useState2("all");
501
515
  const [tab, setTab] = useState2("costs");
502
516
  const selectedPreset = useMemo(() => {
503
- const start = dateRange.start;
504
- const end = dateRange.end;
505
- if (start.toDateString() === end.toDateString()) return "today";
506
- const diffMs = end.getTime() - start.getTime();
517
+ if (dateRange.start.toDateString() === dateRange.end.toDateString()) return "today";
518
+ const diffMs = dateRange.end.getTime() - dateRange.start.getTime();
519
+ const diffHours = diffMs / (1e3 * 60 * 60);
520
+ if (diffHours <= 26) return "24h";
507
521
  const diffDays = Math.ceil(diffMs / (1e3 * 60 * 60 * 24));
508
522
  if (diffDays <= 7) return "7d";
509
523
  return "30d";
@@ -512,25 +526,15 @@ var init_UsageStats = __esm({
512
526
  const end = /* @__PURE__ */ new Date();
513
527
  const start = /* @__PURE__ */ new Date();
514
528
  if (preset === "today") start.setHours(0, 0, 0, 0);
529
+ else if (preset === "24h") start.setHours(start.getHours() - 24);
515
530
  else if (preset === "7d") start.setDate(start.getDate() - 7);
516
531
  else if (preset === "30d") start.setDate(start.getDate() - 30);
532
+ try {
533
+ localStorage.setItem(PERIOD_LS_KEY, preset);
534
+ } catch {
535
+ }
517
536
  onDateRangeChange({ start, end });
518
537
  };
519
- const todayRows = useMemo(() => {
520
- const rows = usage?.daily_by_model ?? [];
521
- if (rows.length === 0) return [];
522
- const latestDate = rows.map((r) => r.date).sort().slice(-1)[0];
523
- return rows.filter((r) => r.date === latestDate).map((r) => ({
524
- model: r.model,
525
- promptTokens: r.prompt_tokens,
526
- completionTokens: r.completion_tokens
527
- })).filter((r) => r.promptTokens + r.completionTokens > 0).sort((a, b) => b.promptTokens + b.completionTokens - (a.promptTokens + a.completionTokens));
528
- }, [usage]);
529
- const todayLabel = useMemo(() => {
530
- const rows = usage?.daily_by_model ?? [];
531
- if (rows.length === 0) return null;
532
- return rows.map((r) => r.date).sort().slice(-1)[0];
533
- }, [usage]);
534
538
  const dailyData = useMemo(
535
539
  () => (usage?.daily_usage ?? []).map((d) => ({
536
540
  date: d.date,
@@ -544,6 +548,35 @@ var init_UsageStats = __esm({
544
548
  })),
545
549
  [usage]
546
550
  );
551
+ const cumulativeData = useMemo(() => {
552
+ let cum = 0;
553
+ return dailyData.map((d) => {
554
+ cum += d.spend;
555
+ return { date: d.date, cumulative: cum };
556
+ });
557
+ }, [dailyData]);
558
+ const successRateData = useMemo(
559
+ () => dailyData.filter((d) => d.apiRequests > 0).map((d) => ({
560
+ date: d.date,
561
+ successRate: parseFloat((d.successfulRequests / d.apiRequests * 100).toFixed(1))
562
+ })),
563
+ [dailyData]
564
+ );
565
+ const { modelSpendByDate, topModels: topSpendModels } = useMemo(() => {
566
+ const rows = usage?.daily_by_model ?? [];
567
+ const modelTotals = {};
568
+ for (const r of rows) modelTotals[r.model] = (modelTotals[r.model] ?? 0) + r.spend;
569
+ const top = Object.entries(modelTotals).sort(([, a], [, b]) => b - a).slice(0, TOP_N_MODELS).map(([m]) => m);
570
+ const dateMap = {};
571
+ for (const r of rows) {
572
+ if (!dateMap[r.date]) dateMap[r.date] = {};
573
+ const bucket = top.includes(r.model) ? r.model : "Other";
574
+ dateMap[r.date][bucket] = (dateMap[r.date][bucket] ?? 0) + r.spend;
575
+ }
576
+ const sorted = Object.entries(dateMap).sort(([a], [b]) => a.localeCompare(b)).map(([date, m]) => ({ date, ...m }));
577
+ const hasOther = sorted.some((r) => r.Other > 0);
578
+ return { modelSpendByDate: sorted, topModels: hasOther ? [...top, "Other"] : top };
579
+ }, [usage]);
547
580
  const modelRows = useMemo(() => {
548
581
  const entries = Object.entries(usage?.usage_by_model ?? {}).map(([model, d]) => ({
549
582
  model,
@@ -558,6 +591,10 @@ var init_UsageStats = __esm({
558
591
  }));
559
592
  return selectedModel === "all" ? entries : entries.filter((e) => e.model === selectedModel);
560
593
  }, [usage, selectedModel]);
594
+ const topModelSpendBars = useMemo(
595
+ () => [...modelRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
596
+ [modelRows]
597
+ );
561
598
  const keyRows = useMemo(
562
599
  () => Object.entries(usage?.usage_by_key ?? {}).map(([keyHash, d]) => ({
563
600
  keyHash,
@@ -575,11 +612,14 @@ var init_UsageStats = __esm({
575
612
  })),
576
613
  [usage]
577
614
  );
615
+ const topKeySpendBars = useMemo(
616
+ () => [...keyRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
617
+ [keyRows]
618
+ );
578
619
  const totalRequests = usage?.api_requests ?? 0;
579
620
  const overallSuccessRate = totalRequests > 0 ? (usage?.successful_requests ?? 0) / totalRequests : 0;
580
- if (loading) {
581
- return /* @__PURE__ */ React3.createElement(Paper3, { sx: { p: 2 } }, /* @__PURE__ */ React3.createElement(Box3, { display: "flex", justifyContent: "center", p: 4 }, /* @__PURE__ */ React3.createElement(CircularProgress2, null)));
582
- }
621
+ const maxBudget = userInfo?.max_budget ?? 0;
622
+ const totalCumSpend = cumulativeData[cumulativeData.length - 1]?.cumulative ?? 0;
583
623
  return /* @__PURE__ */ React3.createElement(Paper3, { sx: { p: 2 } }, /* @__PURE__ */ React3.createElement(Box3, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 2, flexWrap: "wrap", gap: 2 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "h6" }, "Usage Analytics"), /* @__PURE__ */ React3.createElement(Box3, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap" }, /* @__PURE__ */ React3.createElement(FormControl, { size: "small", sx: { minWidth: 140 } }, /* @__PURE__ */ React3.createElement(InputLabel, null, "Period"), /* @__PURE__ */ React3.createElement(
584
624
  Select,
585
625
  {
@@ -588,6 +628,7 @@ var init_UsageStats = __esm({
588
628
  onChange: (e) => handlePresetChange(e.target.value)
589
629
  },
590
630
  /* @__PURE__ */ React3.createElement(MenuItem2, { value: "today" }, "Today"),
631
+ /* @__PURE__ */ React3.createElement(MenuItem2, { value: "24h" }, "Last 24h"),
591
632
  /* @__PURE__ */ React3.createElement(MenuItem2, { value: "7d" }, "Last 7 days"),
592
633
  /* @__PURE__ */ React3.createElement(MenuItem2, { value: "30d" }, "Last 30 days")
593
634
  )), tab === "models" && /* @__PURE__ */ React3.createElement(FormControl, { size: "small", sx: { minWidth: 180 } }, /* @__PURE__ */ React3.createElement(InputLabel, null, "Model"), /* @__PURE__ */ React3.createElement(
@@ -599,28 +640,39 @@ var init_UsageStats = __esm({
599
640
  },
600
641
  /* @__PURE__ */ React3.createElement(MenuItem2, { value: "all" }, "All Models"),
601
642
  models.map((m) => /* @__PURE__ */ React3.createElement(MenuItem2, { key: m.model_name, value: m.model_name }, m.model_name))
602
- )))), /* @__PURE__ */ React3.createElement(Grid, { container: true, spacing: 2, sx: { mb: 2 } }, /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 6, sm: 3 }, /* @__PURE__ */ React3.createElement(KpiCard, { label: "Total Spend", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 6, sm: 3 }, /* @__PURE__ */ React3.createElement(KpiCard, { label: "Total Requests", value: fmtInt(totalRequests), hint: `${fmtInt(usage?.failed_requests ?? 0)} failed` })), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 6, sm: 3 }, /* @__PURE__ */ React3.createElement(KpiCard, { label: "Success Rate", value: totalRequests > 0 ? fmtPct(overallSuccessRate) : "\u2014" })), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 6, sm: 3 }, /* @__PURE__ */ React3.createElement(
643
+ )))), /* @__PURE__ */ React3.createElement(Grid, { container: true, spacing: 2, sx: { mb: 2 } }, /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ React3.createElement(KpiCard, { label: "Total Spend", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ React3.createElement(KpiCard, { label: "Total Requests", value: fmtInt(totalRequests), hint: `${fmtInt(usage?.failed_requests ?? 0)} failed` })), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ React3.createElement(KpiCard, { label: "Success Rate", value: totalRequests > 0 ? fmtPct(overallSuccessRate) : "\u2014" })), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ React3.createElement(
603
644
  KpiCard,
604
645
  {
605
646
  label: "Total Tokens",
606
647
  value: fmtInt(usage?.total_tokens ?? 0),
607
648
  hint: `${fmtInt(usage?.prompt_tokens ?? 0)} in \xB7 ${fmtInt(usage?.completion_tokens ?? 0)} out`
608
649
  }
609
- ))), /* @__PURE__ */ React3.createElement(Tabs, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 2 } }, /* @__PURE__ */ React3.createElement(Tab, { value: "costs", label: "Costs" }), /* @__PURE__ */ React3.createElement(Tab, { value: "models", label: "Model Activity" }), /* @__PURE__ */ React3.createElement(Tab, { value: "keys", label: "Key Activity" })), tab === "costs" && /* @__PURE__ */ React3.createElement(Grid, { container: true, spacing: 3 }, /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend"), /* @__PURE__ */ React3.createElement(Box3, { height: 260 }, /* @__PURE__ */ React3.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React3.createElement(AreaChart, { data: dailyData }, /* @__PURE__ */ React3.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React3.createElement(XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ React3.createElement(YAxis, { tick: { fontSize: 12 }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ React3.createElement(Tooltip, { formatter: (value) => [`$${value.toFixed(4)}`, "Spend"] }), /* @__PURE__ */ React3.createElement(Area, { type: "monotone", dataKey: "spend", stroke: "#8884d8", fill: "#8884d8", fillOpacity: 0.3 }))))), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Requests"), /* @__PURE__ */ React3.createElement(Box3, { height: 260 }, /* @__PURE__ */ React3.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React3.createElement(BarChart, { data: dailyData }, /* @__PURE__ */ React3.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React3.createElement(XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ React3.createElement(YAxis, { tick: { fontSize: 12 } }), /* @__PURE__ */ React3.createElement(Tooltip, null), /* @__PURE__ */ React3.createElement(Legend, null), /* @__PURE__ */ React3.createElement(Bar, { dataKey: "successfulRequests", name: "Successful", fill: "#82ca9d", stackId: "r" }), /* @__PURE__ */ React3.createElement(Bar, { dataKey: "failedRequests", name: "Failed", fill: "#e57373", stackId: "r" }))))), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, todayLabel ? `Tokens In / Out by Model \u2014 ${todayLabel}` : "Tokens In / Out by Model \u2014 Today"), /* @__PURE__ */ React3.createElement(Box3, { height: 300 }, todayRows.length === 0 ? /* @__PURE__ */ React3.createElement(Box3, { display: "flex", alignItems: "center", justifyContent: "center", height: "100%" }, /* @__PURE__ */ React3.createElement(Typography3, { color: "text.secondary" }, "No token activity for the latest day")) : /* @__PURE__ */ React3.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React3.createElement(BarChart, { data: todayRows, layout: "vertical", margin: { left: 60 } }, /* @__PURE__ */ React3.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React3.createElement(XAxis, { type: "number", tick: { fontSize: 12 }, tickFormatter: fmtInt }), /* @__PURE__ */ React3.createElement(YAxis, { type: "category", dataKey: "model", tick: { fontSize: 11 }, width: 180 }), /* @__PURE__ */ React3.createElement(Tooltip, { formatter: (value) => fmtInt(value) }), /* @__PURE__ */ React3.createElement(Legend, null), /* @__PURE__ */ React3.createElement(Bar, { dataKey: "promptTokens", name: "Input (prompt)", fill: "#8884d8", stackId: "io" }), /* @__PURE__ */ React3.createElement(Bar, { dataKey: "completionTokens", name: "Output (completion)", fill: "#82ca9d", stackId: "io" })))))), tab === "models" && /* @__PURE__ */ React3.createElement(Grid, { container: true, spacing: 3 }, /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Tokens by Model"), /* @__PURE__ */ React3.createElement(Box3, { height: 260 }, /* @__PURE__ */ React3.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React3.createElement(BarChart, { data: modelRows }, /* @__PURE__ */ React3.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React3.createElement(XAxis, { dataKey: "model", tick: { fontSize: 10 }, interval: 0, angle: -15, textAnchor: "end", height: 60 }), /* @__PURE__ */ React3.createElement(YAxis, { tick: { fontSize: 12 } }), /* @__PURE__ */ React3.createElement(Tooltip, null), /* @__PURE__ */ React3.createElement(Legend, null), /* @__PURE__ */ React3.createElement(Bar, { dataKey: "promptTokens", name: "Prompt", fill: "#8884d8", stackId: "t" }), /* @__PURE__ */ React3.createElement(Bar, { dataKey: "completionTokens", name: "Completion", fill: "#82ca9d", stackId: "t" }))))), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React3.createElement(TableContainer2, { component: Paper3, variant: "outlined" }, /* @__PURE__ */ React3.createElement(Table2, { size: "small" }, /* @__PURE__ */ React3.createElement(TableHead2, null, /* @__PURE__ */ React3.createElement(TableRow2, null, /* @__PURE__ */ React3.createElement(TableCell2, null, "Model"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Spend"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Requests"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Success"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Failed"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Prompt"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Completion"), /* @__PURE__ */ React3.createElement(TableCell2, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ React3.createElement(TableBody2, null, modelRows.length === 0 ? /* @__PURE__ */ React3.createElement(TableRow2, null, /* @__PURE__ */ React3.createElement(TableCell2, { colSpan: 8, align: "center" }, "No model activity")) : modelRows.sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ React3.createElement(TableRow2, { key: r.model }, /* @__PURE__ */ React3.createElement(TableCell2, null, r.model), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.promptTokens)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.completionTokens)), /* @__PURE__ */ React3.createElement(TableCell2, null, r.apiRequests > 0 ? /* @__PURE__ */ React3.createElement(Box3, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React3.createElement(
650
+ ))), /* @__PURE__ */ React3.createElement(Tabs, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 2 } }, /* @__PURE__ */ React3.createElement(Tab, { value: "costs", label: "Costs" }), /* @__PURE__ */ React3.createElement(Tab, { value: "models", label: "Model Activity" }), /* @__PURE__ */ React3.createElement(Tab, { value: "keys", label: "Key Activity" })), tab === "costs" && /* @__PURE__ */ React3.createElement(Grid, { container: true, spacing: 3 }, /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend by Model"), loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, null) : modelSpendByDate.length === 0 ? /* @__PURE__ */ React3.createElement(EmptyChart, null) : /* @__PURE__ */ React3.createElement(Box3, { height: 260 }, /* @__PURE__ */ React3.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React3.createElement(AreaChart, { data: modelSpendByDate }, /* @__PURE__ */ React3.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React3.createElement(XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ React3.createElement(YAxis, { tick: { fontSize: 12 }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ React3.createElement(Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, void 0] }), /* @__PURE__ */ React3.createElement(Legend, null), topSpendModels.map((m) => /* @__PURE__ */ React3.createElement(
651
+ Area,
652
+ {
653
+ key: m,
654
+ type: "monotone",
655
+ dataKey: m,
656
+ stackId: "spend",
657
+ stroke: modelColor(m),
658
+ fill: modelColor(m),
659
+ fillOpacity: 0.6
660
+ }
661
+ )))))), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Token Usage"), loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, null) : dailyData.length === 0 ? /* @__PURE__ */ React3.createElement(EmptyChart, null) : /* @__PURE__ */ React3.createElement(Box3, { height: 260 }, /* @__PURE__ */ React3.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React3.createElement(AreaChart, { data: dailyData }, /* @__PURE__ */ React3.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React3.createElement(XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ React3.createElement(YAxis, { tick: { fontSize: 12 }, tickFormatter: fmtInt }), /* @__PURE__ */ React3.createElement(Tooltip, { formatter: (v) => [fmtInt(v), void 0] }), /* @__PURE__ */ React3.createElement(Legend, null), /* @__PURE__ */ React3.createElement(Area, { type: "monotone", dataKey: "promptTokens", name: "Input (prompt)", stackId: "tok", stroke: "#8884d8", fill: "#8884d8", fillOpacity: 0.5 }), /* @__PURE__ */ React3.createElement(Area, { type: "monotone", dataKey: "completionTokens", name: "Output (completion)", stackId: "tok", stroke: "#82ca9d", fill: "#82ca9d", fillOpacity: 0.5 }))))), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Requests"), loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, null) : dailyData.length === 0 ? /* @__PURE__ */ React3.createElement(EmptyChart, null) : /* @__PURE__ */ React3.createElement(Box3, { height: 260 }, /* @__PURE__ */ React3.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React3.createElement(BarChart, { data: dailyData }, /* @__PURE__ */ React3.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React3.createElement(XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ React3.createElement(YAxis, { tick: { fontSize: 12 } }), /* @__PURE__ */ React3.createElement(Tooltip, null), /* @__PURE__ */ React3.createElement(Legend, null), /* @__PURE__ */ React3.createElement(Bar, { dataKey: "successfulRequests", name: "Successful", fill: "#82ca9d", stackId: "r" }), /* @__PURE__ */ React3.createElement(Bar, { dataKey: "failedRequests", name: "Failed", fill: "#e57373", stackId: "r" }))))), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Success Rate"), loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, null) : successRateData.length === 0 ? /* @__PURE__ */ React3.createElement(EmptyChart, null) : /* @__PURE__ */ React3.createElement(Box3, { height: 260 }, /* @__PURE__ */ React3.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React3.createElement(LineChart, { data: successRateData }, /* @__PURE__ */ React3.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React3.createElement(XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ React3.createElement(YAxis, { domain: [0, 100], tick: { fontSize: 12 }, tickFormatter: (v) => `${v}%` }), /* @__PURE__ */ React3.createElement(Tooltip, { formatter: (v) => [`${v}%`, "Success rate"] }), /* @__PURE__ */ React3.createElement(ReferenceLine, { y: 100, stroke: "#82ca9d", strokeDasharray: "4 2" }), /* @__PURE__ */ React3.createElement(Line, { type: "monotone", dataKey: "successRate", name: "Success rate", stroke: "#8884d8", dot: { r: 3 } }))))), (maxBudget > 0 || cumulativeData.length > 0) && /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Cumulative Spend", maxBudget > 0 ? ` vs Budget (${fmtUsd(maxBudget)})` : "", maxBudget > 0 && /* @__PURE__ */ React3.createElement(Typography3, { component: "span", variant: "caption", color: "text.secondary", sx: { ml: 1 } }, fmtUsd(totalCumSpend), " used \xB7 ", fmtUsd(Math.max(0, maxBudget - totalCumSpend)), " remaining")), loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, { height: 180 }) : cumulativeData.length === 0 ? /* @__PURE__ */ React3.createElement(EmptyChart, { height: 180 }) : /* @__PURE__ */ React3.createElement(Box3, { height: 180 }, /* @__PURE__ */ React3.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React3.createElement(AreaChart, { data: cumulativeData }, /* @__PURE__ */ React3.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React3.createElement(XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ React3.createElement(YAxis, { tick: { fontSize: 12 }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ React3.createElement(Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, "Cumulative spend"] }), maxBudget > 0 && /* @__PURE__ */ React3.createElement(ReferenceLine, { y: maxBudget, stroke: "#e57373", strokeDasharray: "6 3", label: { value: `Budget ${fmtUsd(maxBudget)}`, position: "insideTopRight", fontSize: 11 } }), /* @__PURE__ */ React3.createElement(Area, { type: "monotone", dataKey: "cumulative", name: "Cumulative spend", stroke: "#ffc658", fill: "#ffc658", fillOpacity: 0.3 })))))), tab === "models" && /* @__PURE__ */ React3.createElement(Grid, { container: true, spacing: 3 }, /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Spend by Model"), loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, null) : topModelSpendBars.length === 0 ? /* @__PURE__ */ React3.createElement(EmptyChart, null) : /* @__PURE__ */ React3.createElement(Box3, { height: Math.max(200, topModelSpendBars.length * 36) }, /* @__PURE__ */ React3.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React3.createElement(BarChart, { data: topModelSpendBars, layout: "vertical", margin: { left: 20 } }, /* @__PURE__ */ React3.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React3.createElement(XAxis, { type: "number", tick: { fontSize: 12 }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ React3.createElement(YAxis, { type: "category", dataKey: "model", tick: { fontSize: 11 }, width: 200 }), /* @__PURE__ */ React3.createElement(Tooltip, { formatter: (v) => [fmtUsd(v), "Spend"] }), /* @__PURE__ */ React3.createElement(Bar, { dataKey: "spend", name: "Spend", radius: [0, 4, 4, 0] }, topModelSpendBars.map((r) => /* @__PURE__ */ React3.createElement("rect", { key: r.model, fill: modelColor(r.model) }))))))), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Tokens by Model"), loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, null) : modelRows.length === 0 ? /* @__PURE__ */ React3.createElement(EmptyChart, null) : /* @__PURE__ */ React3.createElement(Box3, { height: 260 }, /* @__PURE__ */ React3.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React3.createElement(BarChart, { data: modelRows }, /* @__PURE__ */ React3.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React3.createElement(XAxis, { dataKey: "model", tick: { fontSize: 10 }, interval: 0, angle: -15, textAnchor: "end", height: 60 }), /* @__PURE__ */ React3.createElement(YAxis, { tick: { fontSize: 12 } }), /* @__PURE__ */ React3.createElement(Tooltip, null), /* @__PURE__ */ React3.createElement(Legend, null), /* @__PURE__ */ React3.createElement(Bar, { dataKey: "promptTokens", name: "Prompt", fill: "#8884d8", stackId: "t" }), /* @__PURE__ */ React3.createElement(Bar, { dataKey: "completionTokens", name: "Completion", fill: "#82ca9d", stackId: "t" }))))), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React3.createElement(TableContainer2, { component: Paper3, variant: "outlined" }, /* @__PURE__ */ React3.createElement(Table2, { size: "small" }, /* @__PURE__ */ React3.createElement(TableHead2, null, /* @__PURE__ */ React3.createElement(TableRow2, null, /* @__PURE__ */ React3.createElement(TableCell2, null, "Model"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Spend"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Requests"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Success"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Failed"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Prompt"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Completion"), /* @__PURE__ */ React3.createElement(TableCell2, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ React3.createElement(TableBody2, null, loading ? /* @__PURE__ */ React3.createElement(TableRow2, null, /* @__PURE__ */ React3.createElement(TableCell2, { colSpan: 8 }, /* @__PURE__ */ React3.createElement(LinearProgress2, null))) : modelRows.length === 0 ? /* @__PURE__ */ React3.createElement(TableRow2, null, /* @__PURE__ */ React3.createElement(TableCell2, { colSpan: 8, align: "center" }, "No model activity")) : [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ React3.createElement(TableRow2, { key: r.model }, /* @__PURE__ */ React3.createElement(TableCell2, null, /* @__PURE__ */ React3.createElement(Box3, { display: "flex", alignItems: "center", gap: 0.75 }, /* @__PURE__ */ React3.createElement(Box3, { sx: { width: 10, height: 10, borderRadius: "50%", bgcolor: modelColor(r.model), flexShrink: 0 } }), r.model)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.promptTokens)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.completionTokens)), /* @__PURE__ */ React3.createElement(TableCell2, null, r.apiRequests > 0 ? /* @__PURE__ */ React3.createElement(Box3, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React3.createElement(
610
662
  LinearProgress2,
611
663
  {
612
664
  variant: "determinate",
613
665
  value: r.successRate * 100,
614
666
  sx: { flex: 1, height: 6, borderRadius: 3 }
615
667
  }
616
- ), /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))))), tab === "keys" && /* @__PURE__ */ React3.createElement(TableContainer2, { component: Paper3, variant: "outlined" }, /* @__PURE__ */ React3.createElement(Table2, { size: "small" }, /* @__PURE__ */ React3.createElement(TableHead2, null, /* @__PURE__ */ React3.createElement(TableRow2, null, /* @__PURE__ */ React3.createElement(TableCell2, null, "Key"), /* @__PURE__ */ React3.createElement(TableCell2, null, "Models"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Spend"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Requests"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Tokens"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Failed"), /* @__PURE__ */ React3.createElement(TableCell2, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ React3.createElement(TableBody2, null, keyRows.length === 0 ? /* @__PURE__ */ React3.createElement(TableRow2, null, /* @__PURE__ */ React3.createElement(TableCell2, { colSpan: 7, align: "center" }, "No key activity")) : keyRows.sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ React3.createElement(TableRow2, { key: r.keyHash }, /* @__PURE__ */ React3.createElement(TableCell2, null, /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2" }, r.keyAlias), r.teamId ? /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ React3.createElement(TableCell2, null, /* @__PURE__ */ React3.createElement(Box3, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.map((m) => /* @__PURE__ */ React3.createElement(Chip3, { key: m, label: m, size: "small", variant: "outlined" })))), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.totalTokens)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ React3.createElement(TableCell2, null, r.apiRequests > 0 ? /* @__PURE__ */ React3.createElement(Box3, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React3.createElement(
668
+ ), /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))))), tab === "keys" && /* @__PURE__ */ React3.createElement(Grid, { container: true, spacing: 3 }, (loading || topKeySpendBars.length > 0) && /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Spend by Key"), loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, null) : /* @__PURE__ */ React3.createElement(Box3, { height: Math.max(160, topKeySpendBars.length * 36) }, /* @__PURE__ */ React3.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React3.createElement(BarChart, { data: topKeySpendBars, layout: "vertical", margin: { left: 20 } }, /* @__PURE__ */ React3.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React3.createElement(XAxis, { type: "number", tick: { fontSize: 12 }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ React3.createElement(YAxis, { type: "category", dataKey: "keyAlias", tick: { fontSize: 11 }, width: 160 }), /* @__PURE__ */ React3.createElement(Tooltip, { formatter: (v) => [fmtUsd(v), "Spend"] }), /* @__PURE__ */ React3.createElement(Bar, { dataKey: "spend", name: "Spend", fill: "#8884d8", radius: [0, 4, 4, 0] }))))), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React3.createElement(TableContainer2, { component: Paper3, variant: "outlined" }, /* @__PURE__ */ React3.createElement(Table2, { size: "small" }, /* @__PURE__ */ React3.createElement(TableHead2, null, /* @__PURE__ */ React3.createElement(TableRow2, null, /* @__PURE__ */ React3.createElement(TableCell2, null, "Key"), /* @__PURE__ */ React3.createElement(TableCell2, null, "Models"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Spend"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Requests"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Tokens"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Failed"), /* @__PURE__ */ React3.createElement(TableCell2, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ React3.createElement(TableBody2, null, loading ? /* @__PURE__ */ React3.createElement(TableRow2, null, /* @__PURE__ */ React3.createElement(TableCell2, { colSpan: 7 }, /* @__PURE__ */ React3.createElement(LinearProgress2, null))) : keyRows.length === 0 ? /* @__PURE__ */ React3.createElement(TableRow2, null, /* @__PURE__ */ React3.createElement(TableCell2, { colSpan: 7, align: "center" }, "No key activity")) : [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ React3.createElement(TableRow2, { key: r.keyHash }, /* @__PURE__ */ React3.createElement(TableCell2, null, /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2" }, r.keyAlias), r.teamId ? /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ React3.createElement(TableCell2, null, /* @__PURE__ */ React3.createElement(Box3, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.map((m) => /* @__PURE__ */ React3.createElement(Chip3, { key: m, label: m, size: "small", variant: "outlined" })))), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.totalTokens)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ React3.createElement(TableCell2, null, r.apiRequests > 0 ? /* @__PURE__ */ React3.createElement(Box3, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React3.createElement(
617
669
  LinearProgress2,
618
670
  {
619
671
  variant: "determinate",
620
672
  value: r.successRate * 100,
621
673
  sx: { flex: 1, height: 6, borderRadius: 3 }
622
674
  }
623
- ), /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))));
675
+ ), /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))))));
624
676
  };
625
677
  }
626
678
  });
@@ -641,7 +693,7 @@ import {
641
693
  TableContainer as TableContainer3,
642
694
  Collapse,
643
695
  IconButton as IconButton2,
644
- CircularProgress as CircularProgress3
696
+ CircularProgress as CircularProgress2
645
697
  } from "@mui/material";
646
698
  import { ExpandMore, ExpandLess, Group } from "@mui/icons-material";
647
699
  import {
@@ -673,7 +725,7 @@ var init_TeamUsage = __esm({
673
725
  color: isOver ? "error" : isNear ? "warning" : "primary",
674
726
  sx: { height: 6, borderRadius: 1 }
675
727
  }
676
- )), /* @__PURE__ */ React4.createElement(Collapse, { in: expanded }, /* @__PURE__ */ React4.createElement(Box4, { mt: 2 }, usageLoading ? /* @__PURE__ */ React4.createElement(Box4, { display: "flex", justifyContent: "center", p: 2 }, /* @__PURE__ */ React4.createElement(CircularProgress3, { size: 24 })) : dailyData.length > 0 ? /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend"), /* @__PURE__ */ React4.createElement(Box4, { height: 160 }, /* @__PURE__ */ React4.createElement(ResponsiveContainer2, { width: "100%", height: "100%" }, /* @__PURE__ */ React4.createElement(AreaChart2, { data: dailyData }, /* @__PURE__ */ React4.createElement(CartesianGrid2, { strokeDasharray: "3 3" }), /* @__PURE__ */ React4.createElement(XAxis2, { dataKey: "date", tick: { fontSize: 11 } }), /* @__PURE__ */ React4.createElement(YAxis2, { tick: { fontSize: 11 }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ React4.createElement(Tooltip2, { formatter: (v) => [`$${v.toFixed(4)}`, "Spend"] }), /* @__PURE__ */ React4.createElement(Area2, { type: "monotone", dataKey: "spend", stroke: "#8884d8", fill: "#8884d8", fillOpacity: 0.3 }))))) : null, team.members_with_roles?.length ? /* @__PURE__ */ React4.createElement(Box4, { mt: 2 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Members"), /* @__PURE__ */ React4.createElement(TableContainer3, null, /* @__PURE__ */ React4.createElement(Table3, { size: "small" }, /* @__PURE__ */ React4.createElement(TableHead3, null, /* @__PURE__ */ React4.createElement(TableRow3, null, /* @__PURE__ */ React4.createElement(TableCell3, null, "User"), /* @__PURE__ */ React4.createElement(TableCell3, null, "Role"))), /* @__PURE__ */ React4.createElement(TableBody3, null, team.members_with_roles.map((m) => /* @__PURE__ */ React4.createElement(TableRow3, { key: m.user_id }, /* @__PURE__ */ React4.createElement(TableCell3, { sx: { fontFamily: "monospace", fontSize: 12 } }, m.user_id), /* @__PURE__ */ React4.createElement(TableCell3, null, /* @__PURE__ */ React4.createElement(
728
+ )), /* @__PURE__ */ React4.createElement(Collapse, { in: expanded }, /* @__PURE__ */ React4.createElement(Box4, { mt: 2 }, usageLoading ? /* @__PURE__ */ React4.createElement(Box4, { display: "flex", justifyContent: "center", p: 2 }, /* @__PURE__ */ React4.createElement(CircularProgress2, { size: 24 })) : dailyData.length > 0 ? /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend"), /* @__PURE__ */ React4.createElement(Box4, { height: 160 }, /* @__PURE__ */ React4.createElement(ResponsiveContainer2, { width: "100%", height: "100%" }, /* @__PURE__ */ React4.createElement(AreaChart2, { data: dailyData }, /* @__PURE__ */ React4.createElement(CartesianGrid2, { strokeDasharray: "3 3" }), /* @__PURE__ */ React4.createElement(XAxis2, { dataKey: "date", tick: { fontSize: 11 } }), /* @__PURE__ */ React4.createElement(YAxis2, { tick: { fontSize: 11 }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ React4.createElement(Tooltip2, { formatter: (v) => [`$${v.toFixed(4)}`, "Spend"] }), /* @__PURE__ */ React4.createElement(Area2, { type: "monotone", dataKey: "spend", stroke: "#8884d8", fill: "#8884d8", fillOpacity: 0.3 }))))) : null, team.members_with_roles?.length ? /* @__PURE__ */ React4.createElement(Box4, { mt: 2 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Members"), /* @__PURE__ */ React4.createElement(TableContainer3, null, /* @__PURE__ */ React4.createElement(Table3, { size: "small" }, /* @__PURE__ */ React4.createElement(TableHead3, null, /* @__PURE__ */ React4.createElement(TableRow3, null, /* @__PURE__ */ React4.createElement(TableCell3, null, "User"), /* @__PURE__ */ React4.createElement(TableCell3, null, "Role"))), /* @__PURE__ */ React4.createElement(TableBody3, null, team.members_with_roles.map((m) => /* @__PURE__ */ React4.createElement(TableRow3, { key: m.user_id }, /* @__PURE__ */ React4.createElement(TableCell3, { sx: { fontFamily: "monospace", fontSize: 12 } }, m.user_id), /* @__PURE__ */ React4.createElement(TableCell3, null, /* @__PURE__ */ React4.createElement(
677
729
  Chip4,
678
730
  {
679
731
  label: m.role,
@@ -713,10 +765,24 @@ __export(LiteLLMPage_exports, {
713
765
  LiteLLMPage: () => LiteLLMPage
714
766
  });
715
767
  import React5, { useState as useState4, useCallback, useMemo as useMemo2 } from "react";
716
- import { Grid as Grid2, Box as Box5, Snackbar, Alert, CircularProgress as CircularProgress4, Typography as Typography5, Paper as Paper5 } from "@mui/material";
768
+ import { Grid as Grid2, Box as Box5, Snackbar, Alert, CircularProgress as CircularProgress3, Typography as Typography5, Paper as Paper5 } from "@mui/material";
717
769
  import { useAsync, useAsyncRetry } from "react-use";
718
770
  import { useApi } from "@backstage/core-plugin-api";
719
- var LiteLLMPage;
771
+ function initDateRange() {
772
+ let preset = "7d";
773
+ try {
774
+ preset = localStorage.getItem(PERIOD_LS_KEY2) ?? "7d";
775
+ } catch {
776
+ }
777
+ const end = /* @__PURE__ */ new Date();
778
+ const start = /* @__PURE__ */ new Date();
779
+ if (preset === "today") start.setHours(0, 0, 0, 0);
780
+ else if (preset === "24h") start.setHours(start.getHours() - 24);
781
+ else if (preset === "30d") start.setDate(start.getDate() - 30);
782
+ else start.setDate(start.getDate() - 7);
783
+ return { start, end };
784
+ }
785
+ var PERIOD_LS_KEY2, LiteLLMPage;
720
786
  var init_LiteLLMPage = __esm({
721
787
  "src/components/LiteLLMPage.tsx"() {
722
788
  "use strict";
@@ -725,14 +791,10 @@ var init_LiteLLMPage = __esm({
725
791
  init_UsageStats();
726
792
  init_TeamUsage();
727
793
  init_api();
794
+ PERIOD_LS_KEY2 = "litellm_usage_period";
728
795
  LiteLLMPage = () => {
729
796
  const api = useApi(liteLlmApiRef);
730
- const [dateRange, setDateRange] = useState4(() => {
731
- const end = /* @__PURE__ */ new Date();
732
- const start = /* @__PURE__ */ new Date();
733
- start.setDate(start.getDate() - 7);
734
- return { start, end };
735
- });
797
+ const [dateRange, setDateRange] = useState4(initDateRange);
736
798
  const [snackbar, setSnackbar] = useState4(null);
737
799
  const [teamUsageCache, setTeamUsageCache] = useState4({});
738
800
  const [teamUsageLoading, setTeamUsageLoading] = useState4({});
@@ -855,7 +917,7 @@ var init_LiteLLMPage = __esm({
855
917
  );
856
918
  const isInitialLoading = userLoading && !userInfo;
857
919
  if (isInitialLoading) {
858
- return /* @__PURE__ */ React5.createElement(Box5, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React5.createElement(CircularProgress4, null));
920
+ return /* @__PURE__ */ React5.createElement(Box5, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React5.createElement(CircularProgress3, null));
859
921
  }
860
922
  if (userError || !userInfo) {
861
923
  const isProvisioningEnabled = userError?.body?.provisioning === true;
@@ -892,7 +954,8 @@ var init_LiteLLMPage = __esm({
892
954
  models: allModels ?? [],
893
955
  dateRange,
894
956
  onDateRangeChange: setDateRange,
895
- loading: usageLoading
957
+ loading: usageLoading,
958
+ userInfo
896
959
  }
897
960
  ))), /* @__PURE__ */ React5.createElement(
898
961
  Snackbar,
@@ -959,7 +1022,7 @@ import {
959
1022
  Select as Select2,
960
1023
  MenuItem as MenuItem3,
961
1024
  Grid as Grid3,
962
- CircularProgress as CircularProgress5,
1025
+ CircularProgress as CircularProgress4,
963
1026
  Alert as Alert2
964
1027
  } from "@mui/material";
965
1028
  import { AreaChart as AreaChart3, Area as Area3, ResponsiveContainer as ResponsiveContainer3 } from "recharts";
@@ -1027,7 +1090,7 @@ var LiteLLMHomeWidget = ({
1027
1090
  /* @__PURE__ */ React7.createElement(MenuItem3, { value: "today" }, "Today"),
1028
1091
  /* @__PURE__ */ React7.createElement(MenuItem3, { value: "7d" }, "7d"),
1029
1092
  /* @__PURE__ */ React7.createElement(MenuItem3, { value: "30d" }, "30d")
1030
- ))), loading && /* @__PURE__ */ React7.createElement(Box6, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ React7.createElement(CircularProgress5, { size: 32 })), !loading && error && /* @__PURE__ */ React7.createElement(Alert2, { severity: "error", sx: { mt: 1 } }, error), !loading && !error && /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(Grid3, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ React7.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React7.createElement(Kpi, { label: "USD Spent", value: fmtUsd2(usage?.total_spend ?? 0) })), /* @__PURE__ */ React7.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React7.createElement(Kpi, { label: "Tokens In", value: fmtInt2(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ React7.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React7.createElement(Kpi, { label: "Tokens Out", value: fmtInt2(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ React7.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React7.createElement(Kpi, { label: "Keys", value: fmtInt2(keys.length) }))), hasSparkline && /* @__PURE__ */ React7.createElement(Box6, { height: 120 }, /* @__PURE__ */ React7.createElement(ResponsiveContainer3, { width: "100%", height: "100%" }, /* @__PURE__ */ React7.createElement(AreaChart3, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ React7.createElement(
1093
+ ))), loading && /* @__PURE__ */ React7.createElement(Box6, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ React7.createElement(CircularProgress4, { size: 32 })), !loading && error && /* @__PURE__ */ React7.createElement(Alert2, { severity: "error", sx: { mt: 1 } }, error), !loading && !error && /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(Grid3, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ React7.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React7.createElement(Kpi, { label: "USD Spent", value: fmtUsd2(usage?.total_spend ?? 0) })), /* @__PURE__ */ React7.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React7.createElement(Kpi, { label: "Tokens In", value: fmtInt2(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ React7.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React7.createElement(Kpi, { label: "Tokens Out", value: fmtInt2(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ React7.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React7.createElement(Kpi, { label: "Keys", value: fmtInt2(keys.length) }))), hasSparkline && /* @__PURE__ */ React7.createElement(Box6, { height: 120 }, /* @__PURE__ */ React7.createElement(ResponsiveContainer3, { width: "100%", height: "100%" }, /* @__PURE__ */ React7.createElement(AreaChart3, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ React7.createElement(
1031
1094
  Area3,
1032
1095
  {
1033
1096
  type: "monotone",