@acarmisc/backstage-plugin-litellm 0.3.2 → 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.
@@ -1,11 +1,12 @@
1
1
  import React from 'react';
2
- import { DateRange, UsageMetrics, ModelInfo } from '../types';
2
+ import { DateRange, UsageMetrics, ModelInfo, UserInfo } from '../types';
3
3
  interface UsageStatsProps {
4
4
  usage: UsageMetrics | null;
5
5
  models: ModelInfo[];
6
6
  dateRange: DateRange;
7
7
  onDateRangeChange: (range: DateRange) => void;
8
8
  loading: boolean;
9
+ userInfo?: UserInfo;
9
10
  }
10
11
  export declare const UsageStats: React.FC<UsageStatsProps>;
11
12
  export {};
package/dist/index.cjs.js CHANGED
@@ -125,23 +125,17 @@ var init_api = __esm({
125
125
  });
126
126
 
127
127
  // src/components/DashboardHeader.tsx
128
- var import_react, import_material, import_icons_material, DashboardHeader;
128
+ var import_react, import_material, DashboardHeader;
129
129
  var init_DashboardHeader = __esm({
130
130
  "src/components/DashboardHeader.tsx"() {
131
131
  "use strict";
132
132
  import_react = __toESM(require("react"));
133
133
  import_material = require("@mui/material");
134
- import_icons_material = require("@mui/icons-material");
135
134
  DashboardHeader = ({ userInfo, teams, loading }) => {
136
135
  if (loading) {
137
136
  return /* @__PURE__ */ import_react.default.createElement(import_material.Paper, { sx: { p: 2, mb: 2 } }, /* @__PURE__ */ import_react.default.createElement(import_material.LinearProgress, null));
138
137
  }
139
138
  const displayName = userInfo.user_email ?? userInfo.email ?? userInfo.user_id;
140
- const budget = userInfo.max_budget ?? 0;
141
- const spend = userInfo.spend ?? userInfo.current_spend ?? 0;
142
- const budgetPct = budget > 0 ? Math.min(spend / budget * 100, 100) : 0;
143
- const isOver = budget > 0 && spend >= budget;
144
- const isNear = budget > 0 && spend >= budget * 0.8 && !isOver;
145
139
  return /* @__PURE__ */ import_react.default.createElement(import_material.Paper, { sx: { p: 2, mb: 2 } }, /* @__PURE__ */ import_react.default.createElement(import_material.Box, { display: "flex", alignItems: "flex-start", gap: 2, flexWrap: "wrap" }, /* @__PURE__ */ import_react.default.createElement(import_material.Box, { flexGrow: 1 }, /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "h6" }, displayName), /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "caption", color: "text.secondary" }, userInfo.user_id), teams.length > 0 && /* @__PURE__ */ import_react.default.createElement(import_material.Box, { display: "flex", gap: 0.75, flexWrap: "wrap", mt: 1 }, teams.map((team) => /* @__PURE__ */ import_react.default.createElement(
146
140
  import_material.Chip,
147
141
  {
@@ -151,27 +145,19 @@ var init_DashboardHeader = __esm({
151
145
  variant: "outlined",
152
146
  color: "primary"
153
147
  }
154
- )))), budget > 0 && /* @__PURE__ */ import_react.default.createElement(import_material.Box, { minWidth: 220 }, /* @__PURE__ */ import_react.default.createElement(import_material.Box, { display: "flex", justifyContent: "space-between", mb: 0.5 }, /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "body2" }, "$", spend.toFixed(2), " / $", budget.toFixed(2)), isOver && /* @__PURE__ */ import_react.default.createElement(import_material.Chip, { icon: /* @__PURE__ */ import_react.default.createElement(import_icons_material.Warning, null), label: "Over Budget", size: "small", color: "error" }), isNear && /* @__PURE__ */ import_react.default.createElement(import_material.Chip, { label: "Near Limit", size: "small", color: "warning" })), /* @__PURE__ */ import_react.default.createElement(
155
- import_material.LinearProgress,
156
- {
157
- variant: "determinate",
158
- value: budgetPct,
159
- color: isOver ? "error" : isNear ? "warning" : "primary",
160
- sx: { height: 8, borderRadius: 1 }
161
- }
162
- ))));
148
+ ))))));
163
149
  };
164
150
  }
165
151
  });
166
152
 
167
153
  // src/components/KeysTable.tsx
168
- var import_react2, import_material2, import_icons_material2, maskKey, shortKeyId, formatDate, emptyForm, keyToEditForm, KeysTable;
154
+ var import_react2, import_material2, import_icons_material, maskKey, shortKeyId, formatDate, emptyForm, keyToEditForm, KeysTable;
169
155
  var init_KeysTable = __esm({
170
156
  "src/components/KeysTable.tsx"() {
171
157
  "use strict";
172
158
  import_react2 = __toESM(require("react"));
173
159
  import_material2 = require("@mui/material");
174
- import_icons_material2 = require("@mui/icons-material");
160
+ import_icons_material = require("@mui/icons-material");
175
161
  maskKey = (key) => {
176
162
  if (key.length <= 8) return "***";
177
163
  return `${key.slice(0, 4)}...${key.slice(-4)}`;
@@ -253,7 +239,7 @@ var init_KeysTable = __esm({
253
239
  if (!editingKey) return;
254
240
  setEditSubmitting(true);
255
241
  try {
256
- await onUpdateKey(editingKey.key, editForm);
242
+ await onUpdateKey(editingKey.token ?? editingKey.key, editForm);
257
243
  handleCloseEdit();
258
244
  } catch (error) {
259
245
  console.error("Failed to update key:", error);
@@ -269,7 +255,7 @@ var init_KeysTable = __esm({
269
255
  {
270
256
  variant: "contained",
271
257
  color: "primary",
272
- startIcon: /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Add, null),
258
+ startIcon: /* @__PURE__ */ import_react2.default.createElement(import_icons_material.Add, null),
273
259
  onClick: () => setGenerateModalOpen(true)
274
260
  },
275
261
  "Generate New Key"
@@ -291,7 +277,7 @@ var init_KeysTable = __esm({
291
277
  }
292
278
  },
293
279
  shortKeyId(keyId)
294
- ), /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { size: "small", onClick: () => copyToClipboard(keyId), title: "Copy Key ID" }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.ContentCopy, { fontSize: "small" })))), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, formatDate(key.created_at)), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "$", key.spend?.toFixed(4) || "0.00"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, key.max_budget ? `$${key.max_budget}` : "-"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, key.tpm_limit || "-"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { display: "flex", gap: 0.5, flexWrap: "wrap" }, key.models?.slice(0, 2).map((model) => /* @__PURE__ */ import_react2.default.createElement(import_material2.Chip, { key: model, label: model, size: "small" })), (key.models?.length || 0) > 2 && /* @__PURE__ */ import_react2.default.createElement(import_material2.Chip, { label: `+${(key.models?.length || 0) - 2}`, size: "small", variant: "outlined" }))), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, { align: "right" }, /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { onClick: () => handleOpenEdit(key) }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Edit, { fontSize: "small" })), /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { color: "error", onClick: () => onDeleteKey(key.key) }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Delete, null))));
280
+ ), /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { size: "small", onClick: () => copyToClipboard(keyId), title: "Copy Key ID" }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material.ContentCopy, { fontSize: "small" })))), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, formatDate(key.created_at)), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "$", key.spend?.toFixed(4) || "0.00"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, key.max_budget ? `$${key.max_budget}` : "-"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, key.tpm_limit || "-"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { display: "flex", gap: 0.5, flexWrap: "wrap" }, key.models?.slice(0, 2).map((model) => /* @__PURE__ */ import_react2.default.createElement(import_material2.Chip, { key: model, label: model, size: "small" })), (key.models?.length || 0) > 2 && /* @__PURE__ */ import_react2.default.createElement(import_material2.Chip, { label: `+${(key.models?.length || 0) - 2}`, size: "small", variant: "outlined" }))), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, { align: "right" }, /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { onClick: () => handleOpenEdit(key) }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material.Edit, { fontSize: "small" })), /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { color: "error", onClick: () => onDeleteKey(keyId) }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material.Delete, null))));
295
281
  }))))), /* @__PURE__ */ import_react2.default.createElement(import_material2.Dialog, { open: generateModalOpen, onClose: handleCloseModal, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogTitle, null, newKeyValue ? "Key Generated" : "Generate New Key"), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogContent, null, newKeyValue ? /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { variant: "body2", color: "text.secondary", gutterBottom: true }, "Copy this key now. You won't be able to see it again."), /* @__PURE__ */ import_react2.default.createElement(
296
282
  import_material2.Box,
297
283
  {
@@ -316,7 +302,7 @@ var init_KeysTable = __esm({
316
302
  },
317
303
  newKeyValue
318
304
  ),
319
- /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { onClick: () => copyToClipboard(newKeyValue) }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.ContentCopy, null))
305
+ /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { onClick: () => copyToClipboard(newKeyValue) }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material.ContentCopy, null))
320
306
  )) : /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, /* @__PURE__ */ import_react2.default.createElement(
321
307
  import_material2.TextField,
322
308
  {
@@ -449,31 +435,56 @@ var init_KeysTable = __esm({
449
435
  });
450
436
 
451
437
  // src/components/UsageStats.tsx
452
- var import_react3, import_material3, import_recharts, fmtUsd, fmtInt, fmtPct, KpiCard, UsageStats;
438
+ function modelColor(model) {
439
+ let h = 5381;
440
+ for (let i = 0; i < model.length; i++) h = (h << 5) + h + model.charCodeAt(i) >>> 0;
441
+ return MODEL_COLORS[h % MODEL_COLORS.length];
442
+ }
443
+ var import_react3, import_material3, import_recharts, TOP_N_MODELS, PERIOD_LS_KEY, MODEL_COLORS, fmtUsd, fmtInt, fmtPct, KpiCard, ChartSkeleton, EmptyChart, UsageStats;
453
444
  var init_UsageStats = __esm({
454
445
  "src/components/UsageStats.tsx"() {
455
446
  "use strict";
456
447
  import_react3 = __toESM(require("react"));
457
448
  import_material3 = require("@mui/material");
458
449
  import_recharts = require("recharts");
450
+ TOP_N_MODELS = 6;
451
+ PERIOD_LS_KEY = "litellm_usage_period";
452
+ MODEL_COLORS = [
453
+ "#8884d8",
454
+ "#82ca9d",
455
+ "#ffc658",
456
+ "#ff7300",
457
+ "#0088fe",
458
+ "#00C49F",
459
+ "#FFBB28",
460
+ "#FF8042",
461
+ "#a4de6c",
462
+ "#d0ed57"
463
+ ];
459
464
  fmtUsd = (n) => `$${(n ?? 0).toFixed(n < 1 ? 4 : 2)}`;
460
465
  fmtInt = (n) => (n ?? 0).toLocaleString();
461
466
  fmtPct = (n) => `${(n * 100).toFixed(1)}%`;
462
467
  KpiCard = ({ label, value, hint }) => /* @__PURE__ */ import_react3.default.createElement(import_material3.Paper, { variant: "outlined", sx: { p: 2, height: "100%" } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary" }, label), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "h5", sx: { mt: 0.5 } }, value), hint ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary" }, hint) : null);
468
+ ChartSkeleton = ({ height = 260 }) => /* @__PURE__ */ import_react3.default.createElement(import_material3.Skeleton, { variant: "rectangular", height, sx: { borderRadius: 1 } });
469
+ EmptyChart = ({
470
+ height = 260,
471
+ message = "No data for this period"
472
+ }) => /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height, display: "flex", alignItems: "center", justifyContent: "center" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { color: "text.secondary", variant: "body2" }, message));
463
473
  UsageStats = ({
464
474
  usage,
465
475
  models,
466
476
  dateRange,
467
477
  onDateRangeChange,
468
- loading
478
+ loading,
479
+ userInfo
469
480
  }) => {
470
481
  const [selectedModel, setSelectedModel] = (0, import_react3.useState)("all");
471
482
  const [tab, setTab] = (0, import_react3.useState)("costs");
472
483
  const selectedPreset = (0, import_react3.useMemo)(() => {
473
- const start = dateRange.start;
474
- const end = dateRange.end;
475
- if (start.toDateString() === end.toDateString()) return "today";
476
- const diffMs = end.getTime() - start.getTime();
484
+ if (dateRange.start.toDateString() === dateRange.end.toDateString()) return "today";
485
+ const diffMs = dateRange.end.getTime() - dateRange.start.getTime();
486
+ const diffHours = diffMs / (1e3 * 60 * 60);
487
+ if (diffHours <= 26) return "24h";
477
488
  const diffDays = Math.ceil(diffMs / (1e3 * 60 * 60 * 24));
478
489
  if (diffDays <= 7) return "7d";
479
490
  return "30d";
@@ -482,25 +493,15 @@ var init_UsageStats = __esm({
482
493
  const end = /* @__PURE__ */ new Date();
483
494
  const start = /* @__PURE__ */ new Date();
484
495
  if (preset === "today") start.setHours(0, 0, 0, 0);
496
+ else if (preset === "24h") start.setHours(start.getHours() - 24);
485
497
  else if (preset === "7d") start.setDate(start.getDate() - 7);
486
498
  else if (preset === "30d") start.setDate(start.getDate() - 30);
499
+ try {
500
+ localStorage.setItem(PERIOD_LS_KEY, preset);
501
+ } catch {
502
+ }
487
503
  onDateRangeChange({ start, end });
488
504
  };
489
- const todayRows = (0, import_react3.useMemo)(() => {
490
- const rows = usage?.daily_by_model ?? [];
491
- if (rows.length === 0) return [];
492
- const latestDate = rows.map((r) => r.date).sort().slice(-1)[0];
493
- return rows.filter((r) => r.date === latestDate).map((r) => ({
494
- model: r.model,
495
- promptTokens: r.prompt_tokens,
496
- completionTokens: r.completion_tokens
497
- })).filter((r) => r.promptTokens + r.completionTokens > 0).sort((a, b) => b.promptTokens + b.completionTokens - (a.promptTokens + a.completionTokens));
498
- }, [usage]);
499
- const todayLabel = (0, import_react3.useMemo)(() => {
500
- const rows = usage?.daily_by_model ?? [];
501
- if (rows.length === 0) return null;
502
- return rows.map((r) => r.date).sort().slice(-1)[0];
503
- }, [usage]);
504
505
  const dailyData = (0, import_react3.useMemo)(
505
506
  () => (usage?.daily_usage ?? []).map((d) => ({
506
507
  date: d.date,
@@ -514,6 +515,35 @@ var init_UsageStats = __esm({
514
515
  })),
515
516
  [usage]
516
517
  );
518
+ const cumulativeData = (0, import_react3.useMemo)(() => {
519
+ let cum = 0;
520
+ return dailyData.map((d) => {
521
+ cum += d.spend;
522
+ return { date: d.date, cumulative: cum };
523
+ });
524
+ }, [dailyData]);
525
+ const successRateData = (0, import_react3.useMemo)(
526
+ () => dailyData.filter((d) => d.apiRequests > 0).map((d) => ({
527
+ date: d.date,
528
+ successRate: parseFloat((d.successfulRequests / d.apiRequests * 100).toFixed(1))
529
+ })),
530
+ [dailyData]
531
+ );
532
+ const { modelSpendByDate, topModels: topSpendModels } = (0, import_react3.useMemo)(() => {
533
+ const rows = usage?.daily_by_model ?? [];
534
+ const modelTotals = {};
535
+ for (const r of rows) modelTotals[r.model] = (modelTotals[r.model] ?? 0) + r.spend;
536
+ const top = Object.entries(modelTotals).sort(([, a], [, b]) => b - a).slice(0, TOP_N_MODELS).map(([m]) => m);
537
+ const dateMap = {};
538
+ for (const r of rows) {
539
+ if (!dateMap[r.date]) dateMap[r.date] = {};
540
+ const bucket = top.includes(r.model) ? r.model : "Other";
541
+ dateMap[r.date][bucket] = (dateMap[r.date][bucket] ?? 0) + r.spend;
542
+ }
543
+ const sorted = Object.entries(dateMap).sort(([a], [b]) => a.localeCompare(b)).map(([date, m]) => ({ date, ...m }));
544
+ const hasOther = sorted.some((r) => r.Other > 0);
545
+ return { modelSpendByDate: sorted, topModels: hasOther ? [...top, "Other"] : top };
546
+ }, [usage]);
517
547
  const modelRows = (0, import_react3.useMemo)(() => {
518
548
  const entries = Object.entries(usage?.usage_by_model ?? {}).map(([model, d]) => ({
519
549
  model,
@@ -528,6 +558,10 @@ var init_UsageStats = __esm({
528
558
  }));
529
559
  return selectedModel === "all" ? entries : entries.filter((e) => e.model === selectedModel);
530
560
  }, [usage, selectedModel]);
561
+ const topModelSpendBars = (0, import_react3.useMemo)(
562
+ () => [...modelRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
563
+ [modelRows]
564
+ );
531
565
  const keyRows = (0, import_react3.useMemo)(
532
566
  () => Object.entries(usage?.usage_by_key ?? {}).map(([keyHash, d]) => ({
533
567
  keyHash,
@@ -545,11 +579,14 @@ var init_UsageStats = __esm({
545
579
  })),
546
580
  [usage]
547
581
  );
582
+ const topKeySpendBars = (0, import_react3.useMemo)(
583
+ () => [...keyRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
584
+ [keyRows]
585
+ );
548
586
  const totalRequests = usage?.api_requests ?? 0;
549
587
  const overallSuccessRate = totalRequests > 0 ? (usage?.successful_requests ?? 0) / totalRequests : 0;
550
- if (loading) {
551
- return /* @__PURE__ */ import_react3.default.createElement(import_material3.Paper, { sx: { p: 2 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", justifyContent: "center", p: 4 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.CircularProgress, null)));
552
- }
588
+ const maxBudget = userInfo?.max_budget ?? 0;
589
+ const totalCumSpend = cumulativeData[cumulativeData.length - 1]?.cumulative ?? 0;
553
590
  return /* @__PURE__ */ import_react3.default.createElement(import_material3.Paper, { sx: { p: 2 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 2, flexWrap: "wrap", gap: 2 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "h6" }, "Usage Analytics"), /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.FormControl, { size: "small", sx: { minWidth: 140 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.InputLabel, null, "Period"), /* @__PURE__ */ import_react3.default.createElement(
554
591
  import_material3.Select,
555
592
  {
@@ -558,6 +595,7 @@ var init_UsageStats = __esm({
558
595
  onChange: (e) => handlePresetChange(e.target.value)
559
596
  },
560
597
  /* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "today" }, "Today"),
598
+ /* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "24h" }, "Last 24h"),
561
599
  /* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "7d" }, "Last 7 days"),
562
600
  /* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "30d" }, "Last 30 days")
563
601
  )), tab === "models" && /* @__PURE__ */ import_react3.default.createElement(import_material3.FormControl, { size: "small", sx: { minWidth: 180 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.InputLabel, null, "Model"), /* @__PURE__ */ import_react3.default.createElement(
@@ -569,40 +607,51 @@ var init_UsageStats = __esm({
569
607
  },
570
608
  /* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "all" }, "All Models"),
571
609
  models.map((m) => /* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { key: m.model_name, value: m.model_name }, m.model_name))
572
- )))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { container: true, spacing: 2, sx: { mb: 2 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 6, sm: 3 }, /* @__PURE__ */ import_react3.default.createElement(KpiCard, { label: "Total Spend", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 6, sm: 3 }, /* @__PURE__ */ import_react3.default.createElement(KpiCard, { label: "Total Requests", value: fmtInt(totalRequests), hint: `${fmtInt(usage?.failed_requests ?? 0)} failed` })), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 6, sm: 3 }, /* @__PURE__ */ import_react3.default.createElement(KpiCard, { label: "Success Rate", value: totalRequests > 0 ? fmtPct(overallSuccessRate) : "\u2014" })), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 6, sm: 3 }, /* @__PURE__ */ import_react3.default.createElement(
610
+ )))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { container: true, spacing: 2, sx: { mb: 2 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react3.default.createElement(KpiCard, { label: "Total Spend", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react3.default.createElement(KpiCard, { label: "Total Requests", value: fmtInt(totalRequests), hint: `${fmtInt(usage?.failed_requests ?? 0)} failed` })), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react3.default.createElement(KpiCard, { label: "Success Rate", value: totalRequests > 0 ? fmtPct(overallSuccessRate) : "\u2014" })), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react3.default.createElement(
573
611
  KpiCard,
574
612
  {
575
613
  label: "Total Tokens",
576
614
  value: fmtInt(usage?.total_tokens ?? 0),
577
615
  hint: `${fmtInt(usage?.prompt_tokens ?? 0)} in \xB7 ${fmtInt(usage?.completion_tokens ?? 0)} out`
578
616
  }
579
- ))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tabs, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 2 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Tab, { value: "costs", label: "Costs" }), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tab, { value: "models", label: "Model Activity" }), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tab, { value: "keys", label: "Key Activity" })), tab === "costs" && /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { container: true, spacing: 3 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend"), /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 260 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.AreaChart, { data: dailyData }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { tick: { fontSize: 12 }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (value) => [`$${value.toFixed(4)}`, "Spend"] }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "spend", stroke: "#8884d8", fill: "#8884d8", fillOpacity: 0.3 }))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Requests"), /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 260 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.BarChart, { data: dailyData }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { tick: { fontSize: 12 } }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "successfulRequests", name: "Successful", fill: "#82ca9d", stackId: "r" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "failedRequests", name: "Failed", fill: "#e57373", stackId: "r" }))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, todayLabel ? `Tokens In / Out by Model \u2014 ${todayLabel}` : "Tokens In / Out by Model \u2014 Today"), /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 300 }, todayRows.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", alignItems: "center", justifyContent: "center", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { color: "text.secondary" }, "No token activity for the latest day")) : /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.BarChart, { data: todayRows, layout: "vertical", margin: { left: 60 } }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { type: "number", tick: { fontSize: 12 }, tickFormatter: fmtInt }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "model", tick: { fontSize: 11 }, width: 180 }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (value) => fmtInt(value) }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "promptTokens", name: "Input (prompt)", fill: "#8884d8", stackId: "io" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "completionTokens", name: "Output (completion)", fill: "#82ca9d", stackId: "io" })))))), tab === "models" && /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { container: true, spacing: 3 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Tokens by Model"), /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 260 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.BarChart, { data: modelRows }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "model", tick: { fontSize: 10 }, interval: 0, angle: -15, textAnchor: "end", height: 60 }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { tick: { fontSize: 12 } }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "promptTokens", name: "Prompt", fill: "#8884d8", stackId: "t" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "completionTokens", name: "Completion", fill: "#82ca9d", stackId: "t" }))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableContainer, { component: import_material3.Paper, variant: "outlined" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Table, { size: "small" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableHead, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, "Model"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Spend"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Requests"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Success"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Failed"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Prompt"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Completion"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableBody, null, modelRows.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { colSpan: 8, align: "center" }, "No model activity")) : modelRows.sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, { key: r.model }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, r.model), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.promptTokens)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.completionTokens)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, r.apiRequests > 0 ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react3.default.createElement(
617
+ ))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tabs, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 2 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Tab, { value: "costs", label: "Costs" }), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tab, { value: "models", label: "Model Activity" }), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tab, { value: "keys", label: "Key Activity" })), tab === "costs" && /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { container: true, spacing: 3 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend by Model"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : modelSpendByDate.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 260 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.AreaChart, { data: modelSpendByDate }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { tick: { fontSize: 12 }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, void 0] }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Legend, null), topSpendModels.map((m) => /* @__PURE__ */ import_react3.default.createElement(
618
+ import_recharts.Area,
619
+ {
620
+ key: m,
621
+ type: "monotone",
622
+ dataKey: m,
623
+ stackId: "spend",
624
+ stroke: modelColor(m),
625
+ fill: modelColor(m),
626
+ fillOpacity: 0.6
627
+ }
628
+ )))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Token Usage"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : dailyData.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 260 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.AreaChart, { data: dailyData }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { tick: { fontSize: 12 }, tickFormatter: fmtInt }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (v) => [fmtInt(v), void 0] }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "promptTokens", name: "Input (prompt)", stackId: "tok", stroke: "#8884d8", fill: "#8884d8", fillOpacity: 0.5 }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "completionTokens", name: "Output (completion)", stackId: "tok", stroke: "#82ca9d", fill: "#82ca9d", fillOpacity: 0.5 }))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Requests"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : dailyData.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 260 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.BarChart, { data: dailyData }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { tick: { fontSize: 12 } }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "successfulRequests", name: "Successful", fill: "#82ca9d", stackId: "r" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "failedRequests", name: "Failed", fill: "#e57373", stackId: "r" }))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Success Rate"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : successRateData.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 260 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.LineChart, { data: successRateData }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { domain: [0, 100], tick: { fontSize: 12 }, tickFormatter: (v) => `${v}%` }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (v) => [`${v}%`, "Success rate"] }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.ReferenceLine, { y: 100, stroke: "#82ca9d", strokeDasharray: "4 2" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Line, { type: "monotone", dataKey: "successRate", name: "Success rate", stroke: "#8884d8", dot: { r: 3 } }))))), (maxBudget > 0 || cumulativeData.length > 0) && /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Cumulative Spend", maxBudget > 0 ? ` vs Budget (${fmtUsd(maxBudget)})` : "", maxBudget > 0 && /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { component: "span", variant: "caption", color: "text.secondary", sx: { ml: 1 } }, fmtUsd(totalCumSpend), " used \xB7 ", fmtUsd(Math.max(0, maxBudget - totalCumSpend)), " remaining")), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, { height: 180 }) : cumulativeData.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, { height: 180 }) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 180 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.AreaChart, { data: cumulativeData }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { tick: { fontSize: 12 }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, "Cumulative spend"] }), maxBudget > 0 && /* @__PURE__ */ import_react3.default.createElement(import_recharts.ReferenceLine, { y: maxBudget, stroke: "#e57373", strokeDasharray: "6 3", label: { value: `Budget ${fmtUsd(maxBudget)}`, position: "insideTopRight", fontSize: 11 } }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "cumulative", name: "Cumulative spend", stroke: "#ffc658", fill: "#ffc658", fillOpacity: 0.3 })))))), tab === "models" && /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { container: true, spacing: 3 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Spend by Model"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : topModelSpendBars.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: Math.max(200, topModelSpendBars.length * 36) }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.BarChart, { data: topModelSpendBars, layout: "vertical", margin: { left: 20 } }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { type: "number", tick: { fontSize: 12 }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "model", tick: { fontSize: 11 }, width: 200 }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (v) => [fmtUsd(v), "Spend"] }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "spend", name: "Spend", radius: [0, 4, 4, 0] }, topModelSpendBars.map((r) => /* @__PURE__ */ import_react3.default.createElement("rect", { key: r.model, fill: modelColor(r.model) }))))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Tokens by Model"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : modelRows.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 260 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.BarChart, { data: modelRows }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "model", tick: { fontSize: 10 }, interval: 0, angle: -15, textAnchor: "end", height: 60 }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { tick: { fontSize: 12 } }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "promptTokens", name: "Prompt", fill: "#8884d8", stackId: "t" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "completionTokens", name: "Completion", fill: "#82ca9d", stackId: "t" }))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableContainer, { component: import_material3.Paper, variant: "outlined" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Table, { size: "small" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableHead, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, "Model"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Spend"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Requests"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Success"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Failed"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Prompt"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Completion"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableBody, null, loading ? /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { colSpan: 8 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.LinearProgress, null))) : modelRows.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { colSpan: 8, align: "center" }, "No model activity")) : [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, { key: r.model }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", alignItems: "center", gap: 0.75 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { sx: { width: 10, height: 10, borderRadius: "50%", bgcolor: modelColor(r.model), flexShrink: 0 } }), r.model)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.promptTokens)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.completionTokens)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, r.apiRequests > 0 ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react3.default.createElement(
580
629
  import_material3.LinearProgress,
581
630
  {
582
631
  variant: "determinate",
583
632
  value: r.successRate * 100,
584
633
  sx: { flex: 1, height: 6, borderRadius: 3 }
585
634
  }
586
- ), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))))), tab === "keys" && /* @__PURE__ */ import_react3.default.createElement(import_material3.TableContainer, { component: import_material3.Paper, variant: "outlined" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Table, { size: "small" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableHead, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, "Key"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, "Models"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Spend"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Requests"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Tokens"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Failed"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableBody, null, keyRows.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { colSpan: 7, align: "center" }, "No key activity")) : keyRows.sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, { key: r.keyHash }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "body2" }, r.keyAlias), r.teamId ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.map((m) => /* @__PURE__ */ import_react3.default.createElement(import_material3.Chip, { key: m, label: m, size: "small", variant: "outlined" })))), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.totalTokens)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, r.apiRequests > 0 ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react3.default.createElement(
635
+ ), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))))), tab === "keys" && /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { container: true, spacing: 3 }, (loading || topKeySpendBars.length > 0) && /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Spend by Key"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: Math.max(160, topKeySpendBars.length * 36) }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.BarChart, { data: topKeySpendBars, layout: "vertical", margin: { left: 20 } }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { type: "number", tick: { fontSize: 12 }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "keyAlias", tick: { fontSize: 11 }, width: 160 }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (v) => [fmtUsd(v), "Spend"] }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "spend", name: "Spend", fill: "#8884d8", radius: [0, 4, 4, 0] }))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableContainer, { component: import_material3.Paper, variant: "outlined" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Table, { size: "small" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableHead, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, "Key"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, "Models"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Spend"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Requests"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Tokens"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Failed"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableBody, null, loading ? /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { colSpan: 7 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.LinearProgress, null))) : keyRows.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { colSpan: 7, align: "center" }, "No key activity")) : [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, { key: r.keyHash }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "body2" }, r.keyAlias), r.teamId ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.map((m) => /* @__PURE__ */ import_react3.default.createElement(import_material3.Chip, { key: m, label: m, size: "small", variant: "outlined" })))), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.totalTokens)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, r.apiRequests > 0 ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react3.default.createElement(
587
636
  import_material3.LinearProgress,
588
637
  {
589
638
  variant: "determinate",
590
639
  value: r.successRate * 100,
591
640
  sx: { flex: 1, height: 6, borderRadius: 3 }
592
641
  }
593
- ), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))));
642
+ ), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))))));
594
643
  };
595
644
  }
596
645
  });
597
646
 
598
647
  // src/components/TeamUsage.tsx
599
- var import_react4, import_material4, import_icons_material3, import_recharts2, TeamCard, TeamUsage;
648
+ var import_react4, import_material4, import_icons_material2, import_recharts2, TeamCard, TeamUsage;
600
649
  var init_TeamUsage = __esm({
601
650
  "src/components/TeamUsage.tsx"() {
602
651
  "use strict";
603
652
  import_react4 = __toESM(require("react"));
604
653
  import_material4 = require("@mui/material");
605
- import_icons_material3 = require("@mui/icons-material");
654
+ import_icons_material2 = require("@mui/icons-material");
606
655
  import_recharts2 = require("recharts");
607
656
  TeamCard = ({ team, usage, usageLoading }) => {
608
657
  const [expanded, setExpanded] = (0, import_react4.useState)(false);
@@ -612,7 +661,7 @@ var init_TeamUsage = __esm({
612
661
  const isOver = budget > 0 && spend >= budget;
613
662
  const isNear = budget > 0 && spend >= budget * 0.8 && !isOver;
614
663
  const dailyData = usage?.daily_usage?.map((d) => ({ date: d.date, spend: d.spend })) ?? [];
615
- return /* @__PURE__ */ import_react4.default.createElement(import_material4.Paper, { variant: "outlined", sx: { p: 2, mb: 2 } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react4.default.createElement(import_icons_material3.Group, { color: "action" }), /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { flexGrow: 1 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle1", fontWeight: 600 }, team.team_alias ?? team.team_id), team.models?.length ? /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", gap: 0.5, flexWrap: "wrap", mt: 0.5 }, team.models.map((m) => /* @__PURE__ */ import_react4.default.createElement(import_material4.Chip, { key: m, label: m, size: "small", variant: "outlined" }))) : /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "caption", color: "text.secondary" }, "All models")), /* @__PURE__ */ import_react4.default.createElement(import_material4.IconButton, { size: "small", onClick: () => setExpanded((e) => !e) }, expanded ? /* @__PURE__ */ import_react4.default.createElement(import_icons_material3.ExpandLess, null) : /* @__PURE__ */ import_react4.default.createElement(import_icons_material3.ExpandMore, null))), budget > 0 && /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { mt: 1.5 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", justifyContent: "space-between", mb: 0.5 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "body2" }, "$", spend.toFixed(2), " / $", budget.toFixed(2)), isOver && /* @__PURE__ */ import_react4.default.createElement(import_material4.Chip, { label: "Over Budget", size: "small", color: "error" }), isNear && /* @__PURE__ */ import_react4.default.createElement(import_material4.Chip, { label: "Near Limit", size: "small", color: "warning" })), /* @__PURE__ */ import_react4.default.createElement(
664
+ return /* @__PURE__ */ import_react4.default.createElement(import_material4.Paper, { variant: "outlined", sx: { p: 2, mb: 2 } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react4.default.createElement(import_icons_material2.Group, { color: "action" }), /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { flexGrow: 1 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle1", fontWeight: 600 }, team.team_alias ?? team.team_id), team.models?.length ? /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", gap: 0.5, flexWrap: "wrap", mt: 0.5 }, team.models.map((m) => /* @__PURE__ */ import_react4.default.createElement(import_material4.Chip, { key: m, label: m, size: "small", variant: "outlined" }))) : /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "caption", color: "text.secondary" }, "All models")), /* @__PURE__ */ import_react4.default.createElement(import_material4.IconButton, { size: "small", onClick: () => setExpanded((e) => !e) }, expanded ? /* @__PURE__ */ import_react4.default.createElement(import_icons_material2.ExpandLess, null) : /* @__PURE__ */ import_react4.default.createElement(import_icons_material2.ExpandMore, null))), budget > 0 && /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { mt: 1.5 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", justifyContent: "space-between", mb: 0.5 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "body2" }, "$", spend.toFixed(2), " / $", budget.toFixed(2)), isOver && /* @__PURE__ */ import_react4.default.createElement(import_material4.Chip, { label: "Over Budget", size: "small", color: "error" }), isNear && /* @__PURE__ */ import_react4.default.createElement(import_material4.Chip, { label: "Near Limit", size: "small", color: "warning" })), /* @__PURE__ */ import_react4.default.createElement(
616
665
  import_material4.LinearProgress,
617
666
  {
618
667
  variant: "determinate",
@@ -639,7 +688,7 @@ var init_TeamUsage = __esm({
639
688
  return /* @__PURE__ */ import_react4.default.createElement(import_material4.Paper, { sx: { p: 2 } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.LinearProgress, null));
640
689
  }
641
690
  if (!teams.length) {
642
- return /* @__PURE__ */ import_react4.default.createElement(import_material4.Paper, { sx: { p: 2 } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react4.default.createElement(import_icons_material3.Group, { color: "disabled" }), /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { color: "text.secondary", variant: "body2" }, "No team membership found in LiteLLM for this account.")));
691
+ return /* @__PURE__ */ import_react4.default.createElement(import_material4.Paper, { sx: { p: 2 } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react4.default.createElement(import_icons_material2.Group, { color: "disabled" }), /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { color: "text.secondary", variant: "body2" }, "No team membership found in LiteLLM for this account.")));
643
692
  }
644
693
  return /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "h6", mb: 1 }, "Teams"), teams.map((team) => /* @__PURE__ */ import_react4.default.createElement(
645
694
  TeamCard,
@@ -659,7 +708,21 @@ var LiteLLMPage_exports = {};
659
708
  __export(LiteLLMPage_exports, {
660
709
  LiteLLMPage: () => LiteLLMPage
661
710
  });
662
- var import_react5, import_material5, import_react_use, import_core_plugin_api2, LiteLLMPage;
711
+ function initDateRange() {
712
+ let preset = "7d";
713
+ try {
714
+ preset = localStorage.getItem(PERIOD_LS_KEY2) ?? "7d";
715
+ } catch {
716
+ }
717
+ const end = /* @__PURE__ */ new Date();
718
+ const start = /* @__PURE__ */ new Date();
719
+ if (preset === "today") start.setHours(0, 0, 0, 0);
720
+ else if (preset === "24h") start.setHours(start.getHours() - 24);
721
+ else if (preset === "30d") start.setDate(start.getDate() - 30);
722
+ else start.setDate(start.getDate() - 7);
723
+ return { start, end };
724
+ }
725
+ var import_react5, import_material5, import_react_use, import_core_plugin_api2, PERIOD_LS_KEY2, LiteLLMPage;
663
726
  var init_LiteLLMPage = __esm({
664
727
  "src/components/LiteLLMPage.tsx"() {
665
728
  "use strict";
@@ -672,14 +735,10 @@ var init_LiteLLMPage = __esm({
672
735
  init_UsageStats();
673
736
  init_TeamUsage();
674
737
  init_api();
738
+ PERIOD_LS_KEY2 = "litellm_usage_period";
675
739
  LiteLLMPage = () => {
676
740
  const api = (0, import_core_plugin_api2.useApi)(liteLlmApiRef);
677
- const [dateRange, setDateRange] = (0, import_react5.useState)(() => {
678
- const end = /* @__PURE__ */ new Date();
679
- const start = /* @__PURE__ */ new Date();
680
- start.setDate(start.getDate() - 7);
681
- return { start, end };
682
- });
741
+ const [dateRange, setDateRange] = (0, import_react5.useState)(initDateRange);
683
742
  const [snackbar, setSnackbar] = (0, import_react5.useState)(null);
684
743
  const [teamUsageCache, setTeamUsageCache] = (0, import_react5.useState)({});
685
744
  const [teamUsageLoading, setTeamUsageLoading] = (0, import_react5.useState)({});
@@ -839,7 +898,8 @@ var init_LiteLLMPage = __esm({
839
898
  models: allModels ?? [],
840
899
  dateRange,
841
900
  onDateRangeChange: setDateRange,
842
- loading: usageLoading
901
+ loading: usageLoading,
902
+ userInfo
843
903
  }
844
904
  ))), /* @__PURE__ */ import_react5.default.createElement(
845
905
  import_material5.Snackbar,
@@ -872,7 +932,7 @@ module.exports = __toCommonJS(index_exports);
872
932
 
873
933
  // src/plugin.tsx
874
934
  var import_react6 = __toESM(require("react"));
875
- var import_icons_material4 = require("@mui/icons-material");
935
+ var import_icons_material3 = require("@mui/icons-material");
876
936
  var import_frontend_plugin_api = require("@backstage/frontend-plugin-api");
877
937
  init_api();
878
938
  var liteLlmApi = import_frontend_plugin_api.ApiBlueprint.make({
@@ -886,7 +946,7 @@ var liteLlmPage = import_frontend_plugin_api.PageBlueprint.make({
886
946
  params: {
887
947
  path: "/litellm",
888
948
  title: "LiteLLM",
889
- icon: /* @__PURE__ */ import_react6.default.createElement(import_icons_material4.TrendingUp, null),
949
+ icon: /* @__PURE__ */ import_react6.default.createElement(import_icons_material3.TrendingUp, null),
890
950
  loader: async () => {
891
951
  const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
892
952
  return /* @__PURE__ */ import_react6.default.createElement(LiteLLMPage2, null);