@acarmisc/backstage-plugin-litellm 0.37.0 → 0.38.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.cjs.js CHANGED
@@ -317,6 +317,17 @@ var init_api = __esm({
317
317
  end_date: endDate
318
318
  });
319
319
  }
320
+ async getSpendLogs(params) {
321
+ const strParams = {
322
+ start_date: params.startDate,
323
+ end_date: params.endDate
324
+ };
325
+ if (params.userId) strParams.userId = params.userId;
326
+ if (params.teamId) strParams.teamId = params.teamId;
327
+ if (params.page !== void 0) strParams.page = String(params.page);
328
+ if (params.pageSize !== void 0) strParams.page_size = String(params.pageSize);
329
+ return this.get("/insights/spend-logs", strParams);
330
+ }
320
331
  async getConfig() {
321
332
  return this.get("/config");
322
333
  }
@@ -5661,292 +5672,335 @@ var init_ModelsTable = __esm({
5661
5672
  }
5662
5673
  });
5663
5674
 
5664
- // src/components/AuditLog.tsx
5665
- function actionTone(action) {
5666
- if (!action) return "neutral";
5667
- for (const [key, tone] of Object.entries(ACTION_TONES)) {
5668
- if (action.toLowerCase().includes(key)) return tone;
5675
+ // src/insights.ts
5676
+ var costPer1k, summarizeUsage, scorePractices, modelOpportunities;
5677
+ var init_insights = __esm({
5678
+ "src/insights.ts"() {
5679
+ "use strict";
5680
+ costPer1k = (model, spend, tokens, models) => {
5681
+ const info = models?.find((m) => m.model_name === model);
5682
+ const listed = info?.input_cost_per_token != null && info?.output_cost_per_token != null ? (info.input_cost_per_token + info.output_cost_per_token) / 2 * 1e3 : null;
5683
+ if (listed !== null) return listed;
5684
+ return tokens > 0 ? spend / tokens * 1e3 : null;
5685
+ };
5686
+ summarizeUsage = (usage, models, premiumThresholdPer1k = 0.01) => {
5687
+ if (!usage) {
5688
+ return {
5689
+ costPer1kTokens: null,
5690
+ costPerRequest: null,
5691
+ outputInputRatio: null,
5692
+ retryWasteUsd: 0,
5693
+ retryWasteShare: 0,
5694
+ successRate: null,
5695
+ premiumShare: 0,
5696
+ premiumSpend: 0,
5697
+ topModelConcentration: 0,
5698
+ projectedMonthSpend: null,
5699
+ burnMultiple: null
5700
+ };
5701
+ }
5702
+ const {
5703
+ total_spend: spend,
5704
+ total_tokens: tokens,
5705
+ prompt_tokens: prompt,
5706
+ completion_tokens: completion,
5707
+ api_requests: reqs,
5708
+ successful_requests: ok,
5709
+ failed_requests: failed,
5710
+ usage_by_model: byModel
5711
+ } = usage;
5712
+ const wasteShare = reqs > 0 ? failed / reqs : 0;
5713
+ const entries = Object.entries(byModel ?? {});
5714
+ const topSpend = entries.reduce((m, [, d]) => Math.max(m, d.total_spend), 0);
5715
+ const premiumSpend = entries.filter(([name, d]) => (costPer1k(name, d.total_spend, d.total_tokens, models) ?? 0) > premiumThresholdPer1k).reduce((s, [, d]) => s + d.total_spend, 0);
5716
+ const days = usage.daily_usage?.length ?? 0;
5717
+ const projected = days > 0 ? spend / days * 30 : null;
5718
+ return {
5719
+ costPer1kTokens: tokens > 0 ? spend / tokens * 1e3 : null,
5720
+ costPerRequest: reqs > 0 ? spend / reqs : null,
5721
+ outputInputRatio: prompt > 0 ? completion / prompt : null,
5722
+ retryWasteUsd: spend * wasteShare,
5723
+ retryWasteShare: wasteShare,
5724
+ successRate: reqs > 0 ? ok / reqs : null,
5725
+ premiumShare: spend > 0 ? premiumSpend / spend : 0,
5726
+ premiumSpend,
5727
+ topModelConcentration: spend > 0 ? topSpend / spend : 0,
5728
+ projectedMonthSpend: projected,
5729
+ burnMultiple: projected !== null && spend > 0 ? projected / spend : null
5730
+ };
5731
+ };
5732
+ scorePractices = (summary, keyStats) => {
5733
+ const waste = summary.retryWasteShare <= 0.01 ? 30 : summary.retryWasteShare <= 0.05 ? 20 : summary.retryWasteShare <= 0.1 ? 10 : 0;
5734
+ const premium = summary.premiumShare <= 0.2 ? 25 : summary.premiumShare <= 0.5 ? 15 : summary.premiumShare <= 0.8 ? 5 : 0;
5735
+ const conc = summary.topModelConcentration <= 0.5 ? 20 : summary.topModelConcentration <= 0.8 ? 10 : 0;
5736
+ const bad = keyStats.totalKeys > 0 ? (keyStats.idleKeys + keyStats.uncappedKeys) / keyStats.totalKeys : 0;
5737
+ const hygiene = keyStats.totalKeys === 0 ? 25 : bad === 0 ? 25 : bad <= 0.2 ? 15 : bad <= 0.5 ? 5 : 0;
5738
+ const checks = [
5739
+ { id: "retry-waste", label: "Low retry waste", points: waste, maxPoints: 30, detail: `${(summary.retryWasteShare * 100).toFixed(1)}% of requests failed` },
5740
+ { id: "premium-mix", label: "Balanced model mix", points: premium, maxPoints: 25, detail: `${(summary.premiumShare * 100).toFixed(1)}% of spend on premium models` },
5741
+ { id: "concentration", label: "No single-model lock-in", points: conc, maxPoints: 20, detail: `Top model is ${(summary.topModelConcentration * 100).toFixed(1)}% of spend` },
5742
+ { id: "key-hygiene", label: "Key hygiene", points: hygiene, maxPoints: 25, detail: `${keyStats.idleKeys} idle, ${keyStats.uncappedKeys} uncapped of ${keyStats.totalKeys} keys` }
5743
+ ];
5744
+ return { total: checks.reduce((s, c) => s + c.points, 0), checks };
5745
+ };
5746
+ modelOpportunities = (usage, models, premiumThresholdPer1k = 0.01) => {
5747
+ if (!usage) return [];
5748
+ const entries = Object.entries(usage.usage_by_model ?? {});
5749
+ const overallRate = usage.api_requests > 0 ? usage.failed_requests / usage.api_requests : 0;
5750
+ return entries.map(([model, d]) => {
5751
+ const failureRate = d.api_requests > 0 ? d.failed_requests / d.api_requests : null;
5752
+ const avgCostPerRequest = d.api_requests > 0 ? d.total_spend / d.api_requests : 0;
5753
+ const completionShare = d.total_tokens > 0 ? d.completion_tokens / d.total_tokens : 0;
5754
+ const per1k = costPer1k(model, d.total_spend, d.total_tokens, models) ?? 0;
5755
+ const spendShare = usage.total_spend > 0 ? d.total_spend / usage.total_spend : 0;
5756
+ if (failureRate !== null && overallRate > 0 && failureRate > 2 * overallRate && d.api_requests >= 10) {
5757
+ return { model, spend: d.total_spend, requests: d.api_requests, failureRate, avgCostPerRequest, flag: "failure-outlier", hint: `Fails ${(failureRate * 100).toFixed(1)}% vs ${(overallRate * 100).toFixed(1)}% overall` };
5758
+ }
5759
+ if (per1k > premiumThresholdPer1k && completionShare < 0.3 && spendShare > 0.05) {
5760
+ return { model, spend: d.total_spend, requests: d.api_requests, failureRate, avgCostPerRequest, flag: "downgrade-candidate", hint: `$${per1k.toFixed(3)}/1k with ${(completionShare * 100).toFixed(0)}% output \u2014 try a cheaper model` };
5761
+ }
5762
+ return { model, spend: d.total_spend, requests: d.api_requests, failureRate, avgCostPerRequest, flag: "ok", hint: "No action" };
5763
+ });
5764
+ };
5669
5765
  }
5670
- return "info";
5671
- }
5672
- function prettyTableName(name) {
5673
- if (!name) return "\u2014";
5674
- return TABLE_LABELS[name] ?? name;
5766
+ });
5767
+
5768
+ // src/dates.ts
5769
+ function toLocalDay(d) {
5770
+ const year = d.getFullYear();
5771
+ const month = String(d.getMonth() + 1).padStart(2, "0");
5772
+ const day = String(d.getDate()).padStart(2, "0");
5773
+ return `${year}-${month}-${day}`;
5675
5774
  }
5676
- function formatDateTime(iso) {
5677
- try {
5678
- return new Date(iso).toLocaleString();
5679
- } catch {
5680
- return iso;
5681
- }
5775
+ function monthToDateRange(now = /* @__PURE__ */ new Date()) {
5776
+ const start = new Date(now.getFullYear(), now.getMonth(), 1);
5777
+ return {
5778
+ startDate: toLocalDay(start),
5779
+ endDate: toLocalDay(now)
5780
+ };
5682
5781
  }
5683
- function renderAuditLogBody(loading, entries) {
5684
- if (loading) {
5685
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableRow6.default, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { colSpan: 6, sx: { py: 2 }, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Skeleton6.default, { variant: "rounded", height: 160 }) }) });
5686
- }
5687
- if (entries.length === 0) {
5688
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableRow6.default, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { colSpan: 6, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5689
- EmptyState,
5690
- {
5691
- message: "No audit events found",
5692
- hint: "Try widening the filters or a different time window."
5693
- }
5694
- ) }) });
5782
+ var init_dates = __esm({
5783
+ "src/dates.ts"() {
5784
+ "use strict";
5695
5785
  }
5696
- return entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DetailRow, { entry }, entry.id));
5697
- }
5698
- var import_react12, import_Box16, import_Typography14, import_Table6, import_TableBody6, import_TableCell6, import_TableContainer6, import_TableHead6, import_TableRow6, import_TablePagination, import_TextField6, import_MenuItem5, import_Skeleton6, import_Collapse3, import_IconButton7, import_Alert5, import_styles12, import_icons_material9, import_react_use, import_jsx_runtime17, ACTION_TONES, TABLE_LABELS, DetailRow, AuditLog;
5699
- var init_AuditLog = __esm({
5700
- "src/components/AuditLog.tsx"() {
5786
+ });
5787
+
5788
+ // src/components/InsightsTab.tsx
5789
+ var import_react12, import_Box16, import_Grid2, import_Typography14, import_Alert5, import_Button7, import_Table6, import_TableBody6, import_TableCell6, import_TableContainer6, import_TableHead6, import_TableRow6, import_Skeleton6, import_react_use, import_jsx_runtime17, FLAG_TONE, FLAG_LABEL, fmtRatio, fmtPct1, failedRow, InsightsTab;
5790
+ var init_InsightsTab = __esm({
5791
+ "src/components/InsightsTab.tsx"() {
5701
5792
  "use strict";
5702
5793
  import_react12 = require("react");
5703
5794
  import_Box16 = __toESM(require("@mui/material/Box"));
5795
+ import_Grid2 = __toESM(require("@mui/material/Grid"));
5704
5796
  import_Typography14 = __toESM(require("@mui/material/Typography"));
5797
+ import_Alert5 = __toESM(require("@mui/material/Alert"));
5798
+ import_Button7 = __toESM(require("@mui/material/Button"));
5705
5799
  import_Table6 = __toESM(require("@mui/material/Table"));
5706
5800
  import_TableBody6 = __toESM(require("@mui/material/TableBody"));
5707
5801
  import_TableCell6 = __toESM(require("@mui/material/TableCell"));
5708
5802
  import_TableContainer6 = __toESM(require("@mui/material/TableContainer"));
5709
5803
  import_TableHead6 = __toESM(require("@mui/material/TableHead"));
5710
5804
  import_TableRow6 = __toESM(require("@mui/material/TableRow"));
5711
- import_TablePagination = __toESM(require("@mui/material/TablePagination"));
5712
- import_TextField6 = __toESM(require("@mui/material/TextField"));
5713
- import_MenuItem5 = __toESM(require("@mui/material/MenuItem"));
5714
5805
  import_Skeleton6 = __toESM(require("@mui/material/Skeleton"));
5715
- import_Collapse3 = __toESM(require("@mui/material/Collapse"));
5716
- import_IconButton7 = __toESM(require("@mui/material/IconButton"));
5717
- import_Alert5 = __toESM(require("@mui/material/Alert"));
5718
- import_styles12 = require("@mui/material/styles");
5719
- import_icons_material9 = require("@mui/icons-material");
5720
5806
  import_react_use = require("react-use");
5807
+ init_insights();
5808
+ init_format();
5809
+ init_dates();
5721
5810
  init_ui();
5722
5811
  import_jsx_runtime17 = require("react/jsx-runtime");
5723
- ACTION_TONES = {
5724
- created: "success",
5725
- deleted: "danger",
5726
- updated: "warning",
5727
- blocked: "danger",
5728
- unblocked: "success"
5812
+ FLAG_TONE = {
5813
+ "failure-outlier": "danger",
5814
+ "downgrade-candidate": "warning",
5815
+ ok: "success"
5729
5816
  };
5730
- TABLE_LABELS = {
5731
- LiteLLM_VerificationToken: "Key",
5732
- LiteLLM_TeamTable: "Team",
5733
- LiteLLM_TeamMembership: "Team member",
5734
- LiteLLM_ObjectPermissionTable: "Team access (KB / MCP)",
5735
- LiteLLM_UserTable: "User"
5817
+ FLAG_LABEL = {
5818
+ "failure-outlier": "Failure outlier",
5819
+ "downgrade-candidate": "Downgrade candidate",
5820
+ ok: "OK"
5736
5821
  };
5737
- DetailRow = ({ entry }) => {
5738
- const [open, setOpen] = (0, import_react12.useState)(false);
5739
- const hasDetail = entry.before_value || entry.updated_values;
5740
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
5741
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_TableRow6.default, { children: [
5742
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { sx: { width: 40, pr: 0 }, children: hasDetail && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5743
- import_IconButton7.default,
5744
- {
5745
- size: "small",
5746
- onClick: () => setOpen((o) => !o),
5747
- "aria-label": open ? "Hide changes" : "Show changes",
5748
- "aria-expanded": open,
5749
- sx: (theme) => ({
5750
- color: theme.palette.text.secondary,
5751
- transform: open ? "rotate(180deg)" : "none",
5752
- transition: theme.transitions.create("transform")
5753
- }),
5754
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_icons_material9.KeyboardArrowDown, { fontSize: "small" })
5755
- }
5756
- ) }),
5757
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { sx: { whiteSpace: "nowrap" }, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Typography14.default, { variant: "body2", color: "text.secondary", children: formatDateTime(entry.updated_at) }) }),
5758
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { children: entry.action && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(StatusPill, { label: entry.action, tone: actionTone(entry.action) }) }),
5759
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Typography14.default, { variant: "body2", children: prettyTableName(entry.table_name) }) }),
5760
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5761
- import_Typography14.default,
5822
+ fmtRatio = (n) => n === null || Number.isNaN(n) ? "\u2014" : `${n.toFixed(2)}\xD7`;
5823
+ fmtPct1 = (n) => n === null || Number.isNaN(n) ? "\u2014" : `${(n * 100).toFixed(1)}%`;
5824
+ failedRow = (status) => /fail|error/i.test(status ?? "");
5825
+ InsightsTab = ({
5826
+ api,
5827
+ usage,
5828
+ models,
5829
+ keys,
5830
+ dateRange,
5831
+ loading
5832
+ }) => {
5833
+ const summary = (0, import_react12.useMemo)(() => summarizeUsage(usage, models), [usage, models]);
5834
+ const keyStats = (0, import_react12.useMemo)(
5835
+ () => ({
5836
+ idleKeys: keys.filter((k) => (k.spend ?? 0) === 0).length,
5837
+ uncappedKeys: keys.filter((k) => k.max_budget === void 0 || k.max_budget === null).length,
5838
+ totalKeys: keys.length
5839
+ }),
5840
+ [keys]
5841
+ );
5842
+ const score = (0, import_react12.useMemo)(
5843
+ () => scorePractices(summary, keyStats),
5844
+ [summary, keyStats]
5845
+ );
5846
+ const opportunities = (0, import_react12.useMemo)(() => {
5847
+ const opps = modelOpportunities(usage, models);
5848
+ return [...opps].sort(
5849
+ (a, b) => (a.flag === "ok" ? 1 : 0) - (b.flag === "ok" ? 1 : 0)
5850
+ );
5851
+ }, [usage, models]);
5852
+ const {
5853
+ value: spendLogs,
5854
+ loading: logsLoading,
5855
+ error: logsError,
5856
+ retry: retryLogs
5857
+ } = (0, import_react_use.useAsyncRetry)(async () => {
5858
+ return api.getSpendLogs({
5859
+ startDate: toLocalDay(dateRange.start),
5860
+ endDate: toLocalDay(dateRange.end),
5861
+ pageSize: 1e3
5862
+ });
5863
+ }, [api, dateRange]);
5864
+ const logStats = (0, import_react12.useMemo)(() => {
5865
+ const rows = spendLogs?.data ?? [];
5866
+ const hits = rows.filter((r) => r.cacheHit);
5867
+ const cachedPromptTokens = hits.reduce((s, r) => s + (r.promptTokens ?? 0), 0);
5868
+ const priced = models.map((m) => m.input_cost_per_token).filter((p) => p !== null && p !== void 0 && p > 0);
5869
+ const avgInputPrice = priced.length > 0 ? priced.reduce((s, p) => s + p, 0) / priced.length : null;
5870
+ return {
5871
+ total: spendLogs?.total ?? rows.length,
5872
+ hitRate: rows.length > 0 ? hits.length / rows.length : null,
5873
+ // Estimate: cached prompt tokens billed at the average listed input price.
5874
+ savedUsd: avgInputPrice !== null ? cachedPromptTokens * avgInputPrice : null,
5875
+ failedWasteUsd: rows.filter((r) => failedRow(r.status)).reduce((s, r) => s + (r.spend ?? 0), 0),
5876
+ truncated: spendLogs?.truncated ?? false
5877
+ };
5878
+ }, [spendLogs, models]);
5879
+ const renderSpendLogsBody = () => {
5880
+ if (logsLoading) return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Skeleton6.default, { variant: "rounded", height: 120 });
5881
+ if (logsError) {
5882
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Alert5.default, { severity: "error", children: [
5883
+ "Failed to load spend logs: ",
5884
+ logsError.message || "Unknown error",
5885
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Box16.default, { sx: { mt: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Button7.default, { size: "small", onClick: retryLogs, children: "Retry" }) })
5886
+ ] });
5887
+ }
5888
+ if ((spendLogs?.data ?? []).length === 0) {
5889
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(EmptyState, { message: "No spend logs in this period" });
5890
+ }
5891
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Box16.default, { sx: { display: "flex", gap: 3, flexWrap: "wrap" }, children: [
5892
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Stat, { label: "Cache-hit rate", value: fmtPct1(logStats.hitRate) }),
5893
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5894
+ Stat,
5762
5895
  {
5763
- variant: "body2",
5764
- component: "code",
5765
- color: "text.secondary",
5766
- title: entry.object_id ?? void 0,
5767
- sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 },
5768
- children: entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
5896
+ label: "Cache savings (est.)",
5897
+ value: logStats.savedUsd !== null ? fmtUsd(logStats.savedUsd) : "\u2014"
5769
5898
  }
5770
- ) }),
5771
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { children: entry.changed_by ?? "\u2014" })
5772
- ] }),
5773
- hasDetail && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableRow6.default, { sx: { "&&:hover": { bgcolor: "transparent" } }, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { colSpan: 6, sx: { "&&": { py: 0, border: 0 } }, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Collapse3.default, { in: open, unmountOnExit: true, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
5774
- import_Box16.default,
5899
+ ),
5900
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Stat, { label: "Failed-request waste", value: fmtUsd(logStats.failedWasteUsd) }),
5901
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Stat, { label: "Log rows", value: fmtInt(logStats.total) }),
5902
+ logStats.truncated && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Typography14.default, { variant: "caption", color: "text.secondary", children: "Showing the first 1,000 rows \u2014 figures are a lower bound." })
5903
+ ] });
5904
+ };
5905
+ const kpis = [
5906
+ {
5907
+ label: "Cost / 1k tokens",
5908
+ value: summary.costPer1kTokens !== null ? fmtUsd(summary.costPer1kTokens) : "\u2014",
5909
+ tone: "accent"
5910
+ },
5911
+ {
5912
+ label: "Cost / request",
5913
+ value: summary.costPerRequest !== null ? fmtUsd(summary.costPerRequest) : "\u2014",
5914
+ tone: "info"
5915
+ },
5916
+ {
5917
+ label: "Out : in ratio",
5918
+ value: fmtRatio(summary.outputInputRatio),
5919
+ tone: "neutral"
5920
+ },
5921
+ {
5922
+ label: "Retry waste",
5923
+ value: fmtUsd(summary.retryWasteUsd),
5924
+ hint: `${fmtPct1(summary.retryWasteShare)} of spend`,
5925
+ tone: "warning"
5926
+ },
5927
+ {
5928
+ label: "Success rate",
5929
+ value: fmtPct1(summary.successRate),
5930
+ tone: "success"
5931
+ }
5932
+ ];
5933
+ const renderEfficiencyBody = () => {
5934
+ if (loading) return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Skeleton6.default, { variant: "rounded", height: 96 });
5935
+ if (!usage) return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(EmptyState, { message: "No usage in this period" });
5936
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(MetricStrip, { metrics: kpis });
5937
+ };
5938
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Box16.default, { sx: { display: "flex", flexDirection: "column", gap: 2 }, children: [
5939
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5940
+ SectionCard,
5775
5941
  {
5776
- display: "flex",
5777
- gap: 2,
5778
- flexWrap: "wrap",
5779
- sx: (theme) => ({
5780
- p: 2,
5781
- mb: 1.5,
5782
- borderRadius: 1.5,
5783
- bgcolor: (0, import_styles12.alpha)(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.05 : 0.03)
5784
- }),
5785
- children: [
5786
- entry.before_value && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Box16.default, { flex: 1, minWidth: 200, children: [
5787
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5788
- import_Typography14.default,
5789
- {
5790
- variant: "caption",
5791
- color: "text.secondary",
5792
- display: "block",
5793
- mb: 0.75,
5794
- sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" },
5795
- children: "Before"
5796
- }
5797
- ),
5798
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Typography14.default, { component: "pre", variant: "caption", sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", whiteSpace: "pre-wrap", wordBreak: "break-all", m: 0 }, children: JSON.stringify(entry.before_value, null, 2) })
5942
+ title: "Efficiency",
5943
+ subtitle: `${dateRange.start.toLocaleDateString()} \u2013 ${dateRange.end.toLocaleDateString()}`,
5944
+ children: renderEfficiencyBody()
5945
+ }
5946
+ ),
5947
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Grid2.default, { container: true, spacing: 2, alignItems: "flex-start", children: [
5948
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Grid2.default, { item: true, xs: 12, md: 5, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SectionCard, { title: "Practice score", subtitle: "Out of 100", children: loading ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Skeleton6.default, { variant: "rounded", height: 160 }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Box16.default, { sx: { display: "flex", flexDirection: "column", gap: 1.5 }, children: [
5949
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Typography14.default, { sx: { fontSize: 30, fontWeight: 700, fontVariantNumeric: "tabular-nums" }, children: [
5950
+ score.total,
5951
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Typography14.default, { component: "span", color: "text.secondary", sx: { fontSize: 16 }, children: "/100" })
5952
+ ] }),
5953
+ score.checks.map((c) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Box16.default, { children: [
5954
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Box16.default, { sx: { display: "flex", justifyContent: "space-between", gap: 1, mb: 0.5 }, children: [
5955
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Typography14.default, { variant: "body2", sx: { fontWeight: 600 }, children: c.label }),
5956
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Typography14.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" }, children: [
5957
+ c.points,
5958
+ "/",
5959
+ c.maxPoints
5960
+ ] })
5799
5961
  ] }),
5800
- entry.updated_values && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Box16.default, { flex: 1, minWidth: 200, children: [
5801
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5802
- import_Typography14.default,
5803
- {
5804
- variant: "caption",
5805
- color: "text.secondary",
5806
- display: "block",
5807
- mb: 0.75,
5808
- sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" },
5809
- children: "After"
5810
- }
5811
- ),
5812
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Typography14.default, { component: "pre", variant: "caption", sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", whiteSpace: "pre-wrap", wordBreak: "break-all", m: 0 }, children: JSON.stringify(entry.updated_values, null, 2) })
5813
- ] })
5814
- ]
5962
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5963
+ Meter,
5964
+ {
5965
+ value: c.maxPoints > 0 ? c.points / c.maxPoints * 100 : 0,
5966
+ ariaLabel: `${c.label}: ${c.points} of ${c.maxPoints}`
5967
+ }
5968
+ ),
5969
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Typography14.default, { variant: "caption", color: "text.secondary", children: c.detail })
5970
+ ] }, c.id))
5971
+ ] }) }) }),
5972
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Grid2.default, { item: true, xs: 12, md: 7, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SectionCard, { title: "Cache & errors", subtitle: "From spend logs (estimate)", children: renderSpendLogsBody() }) })
5973
+ ] }),
5974
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SectionCard, { title: "Model opportunities", flush: true, children: loading ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Box16.default, { sx: { p: 2.5 }, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Skeleton6.default, { variant: "rounded", height: 120 }) }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableContainer6.default, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Table6.default, { size: "small", sx: dataTableSx, children: [
5975
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableHead6.default, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_TableRow6.default, { children: [
5976
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { children: "Model" }),
5977
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: "Spend" }),
5978
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: "Requests" }),
5979
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: "Failure" }),
5980
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: "Avg / req" }),
5981
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { children: "Signal" })
5982
+ ] }) }),
5983
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableBody6.default, { children: opportunities.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableRow6.default, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { colSpan: 6, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(EmptyState, { message: "No model activity in this period" }) }) }) : opportunities.map((o) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_TableRow6.default, { children: [
5984
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Typography14.default, { variant: "body2", sx: { fontWeight: 600 }, children: o.model }) }),
5985
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: fmtUsd(o.spend) }),
5986
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: fmtInt(o.requests) }),
5987
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: fmtPct1(o.failureRate) }),
5988
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: fmtUsd(o.avgCostPerRequest) }),
5989
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_TableCell6.default, { children: [
5990
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(StatusPill, { label: FLAG_LABEL[o.flag], tone: FLAG_TONE[o.flag], title: o.hint }),
5991
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Typography14.default, { variant: "caption", color: "text.secondary", sx: { display: "block", mt: 0.25 }, children: o.hint })
5992
+ ] })
5993
+ ] }, o.model)) })
5994
+ ] }) }) }),
5995
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SectionCard, { title: "Quality scores", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5996
+ EmptyState,
5997
+ {
5998
+ message: "Quality scores \u2014 connect Langfuse",
5999
+ hint: "LLM-as-judge evaluation is not configured for this deployment."
5815
6000
  }
5816
- ) }) }) })
6001
+ ) })
5817
6002
  ] });
5818
6003
  };
5819
- AuditLog = ({ api }) => {
5820
- const [page, setPage] = (0, import_react12.useState)(0);
5821
- const [pageSize, setPageSize] = (0, import_react12.useState)(25);
5822
- const [filters, setFilters] = (0, import_react12.useState)({});
5823
- const fetchParams = (0, import_react12.useMemo)(
5824
- () => ({ page: page + 1, page_size: pageSize, ...filters }),
5825
- [page, pageSize, filters]
5826
- );
5827
- const { value, loading, error } = (0, import_react_use.useAsync)(
5828
- () => api.getAuditLogs(fetchParams),
5829
- [api, fetchParams]
5830
- );
5831
- const entries = value?.audit_logs ?? [];
5832
- const total = value?.total ?? 0;
5833
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
5834
- SectionCard,
5835
- {
5836
- title: "Audit Log",
5837
- subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
5838
- flush: true,
5839
- actions: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
5840
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
5841
- import_TextField6.default,
5842
- {
5843
- size: "small",
5844
- label: "Action",
5845
- select: true,
5846
- value: filters.action ?? "",
5847
- onChange: (e) => {
5848
- setFilters((f) => ({ ...f, action: e.target.value || void 0 }));
5849
- setPage(0);
5850
- },
5851
- sx: { minWidth: 150 },
5852
- children: [
5853
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_MenuItem5.default, { value: "", children: "All actions" }),
5854
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_MenuItem5.default, { value: "created", children: "Created" }),
5855
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_MenuItem5.default, { value: "updated", children: "Updated" }),
5856
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_MenuItem5.default, { value: "deleted", children: "Deleted" }),
5857
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_MenuItem5.default, { value: "blocked", children: "Blocked" })
5858
- ]
5859
- }
5860
- ),
5861
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
5862
- import_TextField6.default,
5863
- {
5864
- size: "small",
5865
- label: "Table",
5866
- select: true,
5867
- value: filters.table_name ?? "",
5868
- onChange: (e) => {
5869
- setFilters((f) => ({ ...f, table_name: e.target.value || void 0 }));
5870
- setPage(0);
5871
- },
5872
- sx: { minWidth: 150 },
5873
- children: [
5874
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_MenuItem5.default, { value: "", children: "All tables" }),
5875
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_MenuItem5.default, { value: "LiteLLM_VerificationToken", children: "Key" }),
5876
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_MenuItem5.default, { value: "LiteLLM_TeamTable", children: "Team" }),
5877
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_MenuItem5.default, { value: "LiteLLM_TeamMembership", children: "Team member" }),
5878
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_MenuItem5.default, { value: "LiteLLM_ObjectPermissionTable", children: "Team access (KB / MCP)" }),
5879
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_MenuItem5.default, { value: "LiteLLM_UserTable", children: "User" })
5880
- ]
5881
- }
5882
- ),
5883
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5884
- import_TextField6.default,
5885
- {
5886
- size: "small",
5887
- label: "Changed by",
5888
- value: filters.changed_by ?? "",
5889
- onChange: (e) => {
5890
- setFilters((f) => ({ ...f, changed_by: e.target.value || void 0 }));
5891
- setPage(0);
5892
- },
5893
- sx: { minWidth: 180 }
5894
- }
5895
- )
5896
- ] }),
5897
- children: [
5898
- error && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Box16.default, { px: 2.5, pb: 2, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Alert5.default, { severity: "error", children: error.message }) }),
5899
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableContainer6.default, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Table6.default, { size: "small", sx: dataTableSx, children: [
5900
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableHead6.default, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_TableRow6.default, { children: [
5901
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { sx: { width: 40 } }),
5902
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { children: "Time" }),
5903
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { children: "Action" }),
5904
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { children: "Table" }),
5905
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { children: "Object ID" }),
5906
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { children: "Changed By" })
5907
- ] }) }),
5908
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableBody6.default, { children: renderAuditLogBody(loading, entries) })
5909
- ] }) }),
5910
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
5911
- import_TablePagination.default,
5912
- {
5913
- component: "div",
5914
- count: total,
5915
- page,
5916
- onPageChange: (_, p) => setPage(p),
5917
- rowsPerPage: pageSize,
5918
- onRowsPerPageChange: (e) => {
5919
- setPageSize(Number(e.target.value));
5920
- setPage(0);
5921
- },
5922
- rowsPerPageOptions: [10, 25, 50],
5923
- sx: (theme) => ({ borderTop: `1px solid ${theme.palette.divider}` })
5924
- }
5925
- )
5926
- ]
5927
- }
5928
- );
5929
- };
5930
- }
5931
- });
5932
-
5933
- // src/dates.ts
5934
- function toLocalDay(d) {
5935
- const year = d.getFullYear();
5936
- const month = String(d.getMonth() + 1).padStart(2, "0");
5937
- const day = String(d.getDate()).padStart(2, "0");
5938
- return `${year}-${month}-${day}`;
5939
- }
5940
- function monthToDateRange(now = /* @__PURE__ */ new Date()) {
5941
- const start = new Date(now.getFullYear(), now.getMonth(), 1);
5942
- return {
5943
- startDate: toLocalDay(start),
5944
- endDate: toLocalDay(now)
5945
- };
5946
- }
5947
- var init_dates = __esm({
5948
- "src/dates.ts"() {
5949
- "use strict";
5950
6004
  }
5951
6005
  });
5952
6006
 
@@ -6058,18 +6112,18 @@ function initDateRange() {
6058
6112
  else start.setDate(start.getDate() - 7);
6059
6113
  return { start, end };
6060
6114
  }
6061
- var import_react13, import_Box17, import_Alert6, import_Button7, import_Typography15, import_Tabs2, import_Tab2, import_Grid2, import_Accordion2, import_AccordionSummary2, import_AccordionDetails2, import_ExpandMore, import_react_router_dom, import_react_use2, import_core_plugin_api5, import_core_components3, import_plugin_permission_react3, import_backstage_plugin_litellm_common5, import_jsx_runtime18, PERIOD_LS_KEY2, PAGE_TABS, isPageTab, LiteLLMPage;
6115
+ var import_react13, import_Box17, import_Alert6, import_Button8, import_Typography15, import_Tabs2, import_Tab2, import_Grid3, import_Accordion2, import_AccordionSummary2, import_AccordionDetails2, import_ExpandMore, import_react_router_dom, import_react_use2, import_core_plugin_api5, import_core_components3, import_plugin_permission_react3, import_backstage_plugin_litellm_common5, import_jsx_runtime18, PERIOD_LS_KEY2, PAGE_TABS, isPageTab, LiteLLMPage;
6062
6116
  var init_LiteLLMPage = __esm({
6063
6117
  "src/components/LiteLLMPage.tsx"() {
6064
6118
  "use strict";
6065
6119
  import_react13 = require("react");
6066
6120
  import_Box17 = __toESM(require("@mui/material/Box"));
6067
6121
  import_Alert6 = __toESM(require("@mui/material/Alert"));
6068
- import_Button7 = __toESM(require("@mui/material/Button"));
6122
+ import_Button8 = __toESM(require("@mui/material/Button"));
6069
6123
  import_Typography15 = __toESM(require("@mui/material/Typography"));
6070
6124
  import_Tabs2 = __toESM(require("@mui/material/Tabs"));
6071
6125
  import_Tab2 = __toESM(require("@mui/material/Tab"));
6072
- import_Grid2 = __toESM(require("@mui/material/Grid"));
6126
+ import_Grid3 = __toESM(require("@mui/material/Grid"));
6073
6127
  import_Accordion2 = __toESM(require("@mui/material/Accordion"));
6074
6128
  import_AccordionSummary2 = __toESM(require("@mui/material/AccordionSummary"));
6075
6129
  import_AccordionDetails2 = __toESM(require("@mui/material/AccordionDetails"));
@@ -6087,7 +6141,7 @@ var init_LiteLLMPage = __esm({
6087
6141
  init_LiteLLMBudgetWidget();
6088
6142
  init_TeamUsage();
6089
6143
  init_ModelsTable();
6090
- init_AuditLog();
6144
+ init_InsightsTab();
6091
6145
  init_api();
6092
6146
  init_dates();
6093
6147
  import_backstage_plugin_litellm_common5 = require("@acarmisc/backstage-plugin-litellm-common");
@@ -6096,7 +6150,7 @@ var init_LiteLLMPage = __esm({
6096
6150
  init_unprovisionedCopy();
6097
6151
  import_jsx_runtime18 = require("react/jsx-runtime");
6098
6152
  PERIOD_LS_KEY2 = "litellm_usage_period";
6099
- PAGE_TABS = ["overview", "keys", "teams", "models", "audit"];
6153
+ PAGE_TABS = ["overview", "keys", "teams", "models", "insights"];
6100
6154
  isPageTab = (v) => !!v && PAGE_TABS.includes(v);
6101
6155
  LiteLLMPage = () => {
6102
6156
  const api = (0, import_core_plugin_api5.useApi)(liteLlmApiRef);
@@ -6437,7 +6491,7 @@ var init_LiteLLMPage = __esm({
6437
6491
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Tab2.default, { label: "Keys", value: "keys" }),
6438
6492
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Tab2.default, { label: "Teams", value: "teams" }),
6439
6493
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Tab2.default, { label: "Models", value: "models" }),
6440
- userInfo.can_view_audit && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Tab2.default, { label: "Audit Log", value: "audit" })
6494
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Tab2.default, { label: "Insights", value: "insights" })
6441
6495
  ]
6442
6496
  }
6443
6497
  );
@@ -6491,7 +6545,7 @@ var init_LiteLLMPage = __esm({
6491
6545
  }
6492
6546
  ),
6493
6547
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6494
- import_Button7.default,
6548
+ import_Button8.default,
6495
6549
  {
6496
6550
  variant: "contained",
6497
6551
  color: "primary",
@@ -6503,8 +6557,8 @@ var init_LiteLLMPage = __esm({
6503
6557
  }
6504
6558
  ) });
6505
6559
  }
6506
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_Grid2.default, { container: true, spacing: 2, alignItems: "flex-start", children: [
6507
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Grid2.default, { item: true, xs: 12, lg: 8, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6560
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_Grid3.default, { container: true, spacing: 2, alignItems: "flex-start", children: [
6561
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Grid3.default, { item: true, xs: 12, lg: 8, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6508
6562
  UsageStats,
6509
6563
  {
6510
6564
  usage: usage ?? null,
@@ -6518,7 +6572,7 @@ var init_LiteLLMPage = __esm({
6518
6572
  userInfo
6519
6573
  }
6520
6574
  ) }),
6521
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Grid2.default, { item: true, xs: 12, lg: 4, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(LiteLLMBudgetWidget, { compact: true, collapsible: true, userInfo: userInfo ?? null, teams, keys: keys ?? [] }) })
6575
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Grid3.default, { item: true, xs: 12, lg: 4, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(LiteLLMBudgetWidget, { compact: true, collapsible: true, userInfo: userInfo ?? null, teams, keys: keys ?? [] }) })
6522
6576
  ] });
6523
6577
  })(),
6524
6578
  activeTab === "keys" && (() => {
@@ -6526,7 +6580,7 @@ var init_LiteLLMPage = __esm({
6526
6580
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_Alert6.default, { severity: "error", children: [
6527
6581
  "Failed to load keys: ",
6528
6582
  keysError.message || "Unknown error",
6529
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Box17.default, { sx: { mt: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Button7.default, { size: "small", onClick: refreshKeys, children: "Retry" }) })
6583
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Box17.default, { sx: { mt: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Button8.default, { size: "small", onClick: refreshKeys, children: "Retry" }) })
6530
6584
  ] });
6531
6585
  }
6532
6586
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
@@ -6550,7 +6604,7 @@ var init_LiteLLMPage = __esm({
6550
6604
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_Alert6.default, { severity: "error", children: [
6551
6605
  "Failed to load teams: ",
6552
6606
  teamsError.message || "Unknown error",
6553
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Box17.default, { sx: { mt: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Button7.default, { size: "small", onClick: refreshTeams, children: "Retry" }) })
6607
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Box17.default, { sx: { mt: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Button8.default, { size: "small", onClick: refreshTeams, children: "Retry" }) })
6554
6608
  ] });
6555
6609
  }
6556
6610
  const teamsById = /* @__PURE__ */ new Map();
@@ -6587,7 +6641,7 @@ var init_LiteLLMPage = __esm({
6587
6641
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_Alert6.default, { severity: "error", children: [
6588
6642
  "Failed to load models: ",
6589
6643
  modelsError.message || "Unknown error",
6590
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Box17.default, { sx: { mt: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Button7.default, { size: "small", onClick: refreshModels, children: "Retry" }) })
6644
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Box17.default, { sx: { mt: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Button8.default, { size: "small", onClick: refreshModels, children: "Retry" }) })
6591
6645
  ] });
6592
6646
  }
6593
6647
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
@@ -6599,7 +6653,17 @@ var init_LiteLLMPage = __esm({
6599
6653
  }
6600
6654
  );
6601
6655
  })(),
6602
- activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(AuditLog, { api })
6656
+ activeTab === "insights" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6657
+ InsightsTab,
6658
+ {
6659
+ api,
6660
+ usage: usage ?? null,
6661
+ models: allModels ?? [],
6662
+ keys: keys ?? [],
6663
+ dateRange,
6664
+ loading: usageLoading
6665
+ }
6666
+ )
6603
6667
  ] }),
6604
6668
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
6605
6669
  KeyFormDialog,
@@ -6773,14 +6837,14 @@ var ConnectPage_exports = {};
6773
6837
  __export(ConnectPage_exports, {
6774
6838
  ConnectPage: () => ConnectPage
6775
6839
  });
6776
- var import_react14, import_Alert7, import_Box18, import_Button8, import_Checkbox3, import_FormControlLabel3, import_FormGroup, import_Paper9, import_Typography16, import_react_router_dom2, import_react_use3, import_core_plugin_api6, import_core_components4, import_jsx_runtime19, ConnectPage;
6840
+ var import_react14, import_Alert7, import_Box18, import_Button9, import_Checkbox3, import_FormControlLabel3, import_FormGroup, import_Paper9, import_Typography16, import_react_router_dom2, import_react_use3, import_core_plugin_api6, import_core_components4, import_jsx_runtime19, ConnectPage;
6777
6841
  var init_ConnectPage = __esm({
6778
6842
  "src/components/ConnectPage.tsx"() {
6779
6843
  "use strict";
6780
6844
  import_react14 = require("react");
6781
6845
  import_Alert7 = __toESM(require("@mui/material/Alert"));
6782
6846
  import_Box18 = __toESM(require("@mui/material/Box"));
6783
- import_Button8 = __toESM(require("@mui/material/Button"));
6847
+ import_Button9 = __toESM(require("@mui/material/Button"));
6784
6848
  import_Checkbox3 = __toESM(require("@mui/material/Checkbox"));
6785
6849
  import_FormControlLabel3 = __toESM(require("@mui/material/FormControlLabel"));
6786
6850
  import_FormGroup = __toESM(require("@mui/material/FormGroup"));
@@ -6851,7 +6915,7 @@ var init_ConnectPage = __esm({
6851
6915
  )) }),
6852
6916
  error && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_Alert7.default, { severity: "error", sx: { mb: 2 }, children: error }),
6853
6917
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
6854
- import_Button8.default,
6918
+ import_Button9.default,
6855
6919
  {
6856
6920
  variant: "contained",
6857
6921
  onClick: connect,
@@ -6891,7 +6955,7 @@ __export(index_exports, {
6891
6955
  module.exports = __toCommonJS(index_exports);
6892
6956
 
6893
6957
  // src/plugin.tsx
6894
- var import_icons_material10 = require("@mui/icons-material");
6958
+ var import_icons_material9 = require("@mui/icons-material");
6895
6959
  var import_frontend_plugin_api4 = require("@backstage/frontend-plugin-api");
6896
6960
  init_api();
6897
6961
  init_routes();
@@ -6907,7 +6971,7 @@ var liteLlmPage = import_frontend_plugin_api4.PageBlueprint.make({
6907
6971
  params: {
6908
6972
  path: "/litellm",
6909
6973
  title: "LiteLLM",
6910
- icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_icons_material10.TrendingUp, {}),
6974
+ icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_icons_material9.TrendingUp, {}),
6911
6975
  routeRef: rootRouteRef,
6912
6976
  loader: async () => {
6913
6977
  const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
@@ -6947,7 +7011,7 @@ var import_Box19 = __toESM(require("@mui/material/Box"));
6947
7011
  var import_Typography17 = __toESM(require("@mui/material/Typography"));
6948
7012
  var import_FormControl2 = __toESM(require("@mui/material/FormControl"));
6949
7013
  var import_Select2 = __toESM(require("@mui/material/Select"));
6950
- var import_MenuItem6 = __toESM(require("@mui/material/MenuItem"));
7014
+ var import_MenuItem5 = __toESM(require("@mui/material/MenuItem"));
6951
7015
  var import_Skeleton7 = __toESM(require("@mui/material/Skeleton"));
6952
7016
  var import_recharts3 = require("recharts");
6953
7017
  var import_core_plugin_api7 = require("@backstage/core-plugin-api");
@@ -7084,9 +7148,9 @@ var LiteLLMHomeWidget = ({
7084
7148
  displayEmpty: true,
7085
7149
  inputProps: { "aria-label": "Usage period" },
7086
7150
  children: [
7087
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_MenuItem6.default, { value: "today", children: "Today" }),
7088
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_MenuItem6.default, { value: "7d", children: "7d" }),
7089
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_MenuItem6.default, { value: "30d", children: "30d" })
7151
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_MenuItem5.default, { value: "today", children: "Today" }),
7152
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_MenuItem5.default, { value: "7d", children: "7d" }),
7153
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_MenuItem5.default, { value: "30d", children: "30d" })
7090
7154
  ]
7091
7155
  }
7092
7156
  ) })
@@ -7157,13 +7221,13 @@ var import_react16 = require("react");
7157
7221
  var import_frontend_plugin_api6 = require("@backstage/frontend-plugin-api");
7158
7222
  var import_Paper11 = __toESM(require("@mui/material/Paper"));
7159
7223
  var import_Box20 = __toESM(require("@mui/material/Box"));
7160
- var import_Button9 = __toESM(require("@mui/material/Button"));
7224
+ var import_Button10 = __toESM(require("@mui/material/Button"));
7161
7225
  var import_Typography18 = __toESM(require("@mui/material/Typography"));
7162
7226
  var import_Skeleton8 = __toESM(require("@mui/material/Skeleton"));
7163
- var import_Collapse4 = __toESM(require("@mui/material/Collapse"));
7164
- var import_styles13 = require("@mui/material/styles");
7227
+ var import_Collapse3 = __toESM(require("@mui/material/Collapse"));
7228
+ var import_styles12 = require("@mui/material/styles");
7165
7229
  var import_useMediaQuery = __toESM(require("@mui/material/useMediaQuery"));
7166
- var import_icons_material11 = require("@mui/icons-material");
7230
+ var import_icons_material10 = require("@mui/icons-material");
7167
7231
  var import_recharts4 = require("recharts");
7168
7232
  var import_core_plugin_api8 = require("@backstage/core-plugin-api");
7169
7233
  var import_core_components6 = require("@backstage/core-components");
@@ -7574,7 +7638,7 @@ var LiteLLMBudgetGauges = ({
7574
7638
  keys: propKeys
7575
7639
  }) => {
7576
7640
  const api = (0, import_core_plugin_api8.useApi)(liteLlmApiRef);
7577
- const theme = (0, import_styles13.useTheme)();
7641
+ const theme = (0, import_styles12.useTheme)();
7578
7642
  const isSmallScreen = (0, import_useMediaQuery.default)(theme.breakpoints.down("sm"));
7579
7643
  const gaugeSize = isSmallScreen ? Math.min(48, size) : size;
7580
7644
  const expandedRegionId = (0, import_react16.useId)();
@@ -7675,10 +7739,10 @@ var LiteLLMBudgetGauges = ({
7675
7739
  return onCreateKey ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(GenerateKeyButton, { size: "small", label, onClick: onCreateKey }, key) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(GenerateKeyButton, { size: "small", label, to: `${moduleHref}?generate=1` }, key);
7676
7740
  case "module":
7677
7741
  if (!moduleHref) return null;
7678
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_Button9.default, { size: "small", variant: "text", component: import_core_components6.Link, to: moduleHref, children: label }, key);
7742
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_Button10.default, { size: "small", variant: "text", component: import_core_components6.Link, to: moduleHref, children: label }, key);
7679
7743
  case "all-limits":
7680
7744
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7681
- import_Button9.default,
7745
+ import_Button10.default,
7682
7746
  {
7683
7747
  size: "small",
7684
7748
  variant: "text",
@@ -7686,7 +7750,7 @@ var LiteLLMBudgetGauges = ({
7686
7750
  "aria-expanded": isExpanded,
7687
7751
  "aria-controls": expandedRegionId,
7688
7752
  endIcon: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7689
- import_icons_material11.ExpandMore,
7753
+ import_icons_material10.ExpandMore,
7690
7754
  {
7691
7755
  sx: (muiTheme) => ({
7692
7756
  transform: isExpanded ? "rotate(180deg)" : "none",
@@ -7781,7 +7845,7 @@ var LiteLLMBudgetGauges = ({
7781
7845
  maxBudget: user?.max_budget ?? void 0
7782
7846
  }
7783
7847
  ) }),
7784
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_Collapse4.default, { in: isExpanded, unmountOnExit: true, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7848
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_Collapse3.default, { in: isExpanded, unmountOnExit: true, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
7785
7849
  import_Box20.default,
7786
7850
  {
7787
7851
  id: expandedRegionId,